From e5f5a8ce6f2e80ed91105c3f7f4104749e167a9b Mon Sep 17 00:00:00 2001 From: Nick Hastings Date: Sat, 1 Apr 2023 15:07:59 -0400 Subject: [PATCH] Round 1 of base provider split. (Only compilation has been tested) --- core/metamod.cpp | 323 ++------- core/metamod_provider.h | 52 +- core/provider/provider_base.cpp | 742 +-------------------- core/provider/provider_base.h | 62 +- core/provider/source/provider_source.cpp | 619 +++++++++++++++++ core/provider/source/provider_source.h | 65 +- core/provider/source2/provider_source2.cpp | 395 +++++++++++ core/provider/source2/provider_source2.h | 46 +- 8 files changed, 1278 insertions(+), 1026 deletions(-) diff --git a/core/metamod.cpp b/core/metamod.cpp index f8fe017..f383f0c 100644 --- a/core/metamod.cpp +++ b/core/metamod.cpp @@ -35,9 +35,6 @@ #include "metamod_console.h" #include "provider/provider_base.h" #include -#if SOURCE_ENGINE == SE_DOTA -#include -#endif #define X64_SUFFIX ".x64" @@ -50,50 +47,12 @@ using namespace SourceHook::Impl; * @file sourcemm.cpp */ -#if SOURCE_ENGINE == SE_DOTA -// Hack to make hook decl compile when only having forward decl in header. -// (we have class structure but it requires protobuf which we don't want to include here) -class GameSessionConfiguration_t { }; - -SH_DECL_MANUALHOOK3_void(SGD_StartupServer, 0, 0, 0, const GameSessionConfiguration_t &, ISource2WorldSession *, const char *); -SH_DECL_MANUALHOOK2_void(SGD_Init, 0, 0, 0, GameSessionConfiguration_t *, const char *); -SH_DECL_MANUALHOOK3(SGD_StartChangeLevel, 0, 0, 0, CUtlVector *, const char *, const char *, void *); -SH_DECL_MANUALHOOK5_void(SGD_SwitchToLoop, 0, 0, 0, const char *, KeyValues *, uint32, const char *, bool); - -static void -Handler_SwitchToLoop(const char *, KeyValues *, uint32, const char *, bool); - -static void -Handler_StartupServer_Post(const GameSessionConfiguration_t &, ISource2WorldSession *, const char *); - -static void -Handler_Init(GameSessionConfiguration_t *, const char *); - -static CUtlVector * -Handler_StartChangeLevel(const char *, const char *, void *); -#else -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(); -#endif - static void InitializeVSP(); +static void +DoInitialPluginLoads(); + static int LoadPluginsFromFile(const char *filepath, int &skipped); @@ -170,6 +129,73 @@ SourceMM::ISmmAPI *g_pMetamod = &g_Metamod; } \ } +static class ProviderCallbacks : public IMetamodSourceProviderCallbacks +{ + virtual void OnGameInit() override + { + if (is_game_init) + return; + + provider->DisplayDevMsg("MMS: OnGameInit\n"); + + if (vsp_load_requested) + InitializeVSP(); + + if (g_bIsVspBridged && !were_plugins_loaded) + { + DoInitialPluginLoads(); + g_PluginMngr.SetAllLoaded(); + were_plugins_loaded = true; + } + + is_game_init = true; + } + + virtual void OnLevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, + char const* pLandmarkName, bool loadGame, bool background) override + { + provider->DisplayDevMsg("MMS: LevelInit\n"); + + ITER_EVENT(OnLevelInit, (pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background)); + } + + virtual void OnLevelShutdown() override + { + provider->DisplayDevMsg("MMS: LevelShutdown\n"); + + 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, ()); + } +} s_ProviderCallbacks; + /* Initialize everything here */ void mm_InitializeForLoad() @@ -184,46 +210,7 @@ mm_InitializeForLoad() */ in_first_level = true; -#if SOURCE_ENGINE == SE_DOTA - SourceHook::MemFuncInfo info; - - if (!provider->GetHookInfo(ProvidedHook_StartupServer, &info)) - { - provider->DisplayError("Metamod:Source could not find a valid hook for INetworkServerService::StartupServer"); - } - SH_MANUALHOOK_RECONFIGURE(SGD_StartupServer, info.vtblindex, info.vtbloffs, info.thisptroffs); - SH_ADD_MANUALHOOK(SGD_StartupServer, netservice, SH_STATIC(Handler_StartupServer_Post), true); - - if (!provider->GetHookInfo(ProvidedHook_SwitchToLoop, &info)) - { - provider->DisplayError("Metamod:Source could not find a valid hook for IEngineServiceMgr::SwitchToLoop"); - } - SH_MANUALHOOK_RECONFIGURE(SGD_SwitchToLoop, info.vtblindex, info.vtbloffs, info.thisptroffs); - SH_ADD_MANUALHOOK(SGD_SwitchToLoop, enginesvcmgr, SH_STATIC(Handler_SwitchToLoop), false); -#else - 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); -#endif + provider->SetCallbacks(&s_ProviderCallbacks); } bool @@ -531,174 +518,6 @@ mm_UnloadMetamod() g_SourceHook.CompleteShutdown(); } -static void -mm_HandleGameInit() -{ - if (is_game_init) - return; - -#if SOURCE_ENGINE == SE_DOTA - DevMsg("MMS: GameInit\n"); -#endif - - if (vsp_load_requested) - InitializeVSP(); - - if (g_bIsVspBridged && !were_plugins_loaded) - { - DoInitialPluginLoads(); - g_PluginMngr.SetAllLoaded(); - were_plugins_loaded = true; - } - - is_game_init = true; -} - -static void -mm_HandleLevelShutdown() -{ -#if SOURCE_ENGINE == SE_DOTA - DevMsg("MMS: LevelShutdown\n"); -#endif - - 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, ()); -} - -static void -mm_HandleLevelInit(char const *pMapName, -char const *pMapEntities, -char const *pOldLevel, -char const *pLandmarkName, -bool loadGame, -bool background) -{ -#if SOURCE_ENGINE == SE_DOTA - DevMsg("MMS: LevelInit\n"); -#endif - - ITER_EVENT(OnLevelInit, (pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background)); -} -#include -#if SOURCE_ENGINE == SE_DOTA -static void -Handler_SwitchToLoop(const char *pszLoopName, KeyValues *pKV, uint32 nId, const char *pszUnk, bool bUnk) -{ - if (strcmp(pszLoopName, "levelload") == 0) - { - mm_HandleGameInit(); - } - - RETURN_META(MRES_IGNORED); -} - -static void -Handler_StartupServer_Post(const GameSessionConfiguration_t &config, ISource2WorldSession *, const char *) -{ - static bool bGameServerHooked = false; - if (!bGameServerHooked) - { - INetworkGameServer *netserver = (META_IFACEPTR(INetworkServerService))->GetIGameServer(); - - SourceHook::MemFuncInfo info; - if (!provider->GetHookInfo(ProvidedHook_Init, &info)) - { - provider->DisplayError("Metamod:Source could not find a valid hook for INetworkGameServer::Init"); - } - SH_MANUALHOOK_RECONFIGURE(SGD_Init, info.vtblindex, info.vtbloffs, info.thisptroffs); - SH_ADD_MANUALVPHOOK(SGD_Init, netserver, SH_STATIC(Handler_Init), false); - - if (!provider->GetHookInfo(ProvidedHook_StartChangeLevel, &info)) - { - provider->DisplayError("Metamod:Source could not find a valid hook for INetworkGameServer::StartChangeLevel"); - } - SH_MANUALHOOK_RECONFIGURE(SGD_StartChangeLevel, info.vtblindex, info.vtbloffs, info.thisptroffs); - SH_ADD_MANUALVPHOOK(SGD_StartChangeLevel, netserver, SH_STATIC(Handler_StartChangeLevel), false); - - bGameServerHooked = true; - } - - RETURN_META(MRES_IGNORED); -} - -static void -Handler_Init(GameSessionConfiguration_t *pConfig, const char *pszMapName) -{ - static char szLastMap[260] = ""; - mm_HandleLevelInit(pszMapName, "", szLastMap, "", false, false); - UTIL_Format(szLastMap, sizeof(szLastMap), "%s", pszMapName); - - RETURN_META(MRES_IGNORED); -} - -static CUtlVector * -Handler_StartChangeLevel(const char *, const char *, void *) -{ - mm_HandleLevelShutdown(); - - RETURN_META_VALUE(MRES_IGNORED, nullptr); -} - -#else - -static bool -Handler_GameInit() -{ - mm_HandleGameInit(); - - RETURN_META_VALUE(MRES_IGNORED, true); -} - -static void -Handler_LevelShutdown(void) -{ - mm_HandleLevelShutdown(); - - 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); -} -#endif - void MetamodSource::LogMsg(ISmmPlugin *pl, const char *msg, ...) { va_list ap; diff --git a/core/metamod_provider.h b/core/metamod_provider.h index 2451625..30cefc1 100644 --- a/core/metamod_provider.h +++ b/core/metamod_provider.h @@ -49,6 +49,29 @@ namespace SourceMM #endif }; + /** + * @brief Interface for Metamod:Source to provide callbacks to the + * provider. + */ + class IMetamodSourceProviderCallbacks + { + public: + /** + * @brief Called before the server DLL handles game initialization. + */ + virtual void OnGameInit() = 0; + + /** + * @brief Called after the server DLL has completed handling level/map initialization. + */ + virtual void OnLevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, char const* pLandmarkName, bool loadGame, bool background) = 0; + + /** + * @brief Called after the server DLL has completed handling level/map shut down. + */ + virtual void OnLevelShutdown() = 0; + }; + /** * @brief Abstracts command information, since the new engine fixes the * re-entrancy problems in the tokenization system. @@ -83,6 +106,13 @@ namespace SourceMM class IMetamodSourceProvider { public: + /** + * @brief Set the callback interface for the provider to call into. + * + * @param pCallbacks Pointer to callback interface implementation. + */ + virtual void SetCallbacks(IMetamodSourceProviderCallbacks* pCallbacks) = 0; + /** * @brief Returns whether source engine build is compatible. * @@ -91,18 +121,6 @@ namespace SourceMM */ virtual bool IsSourceEngineBuildCompatible(int build) =0; - /** - * @brief Retrieves hook information for each callback. Each hook - * must be implemented. - * - * @param hook Hook information to provide. - * @param pInfo Non-NULL pointer to fill with information - * about the hook's virtual location. - * @return True if supported, false to fail, which - * will cause Metamod:Source to fail. - */ - virtual bool GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo) =0; - /** * @brief Logs a message via IVEngineServer::LogPrint. * @@ -167,6 +185,16 @@ namespace SourceMM */ virtual void DisplayWarning(const char *fmt, ...) =0; + /** + * @brief Sends the server a developer message. + * + * No newline is appended. + * + * @param fmt Formatted message string. + * @param ... Format parameters. + */ + virtual void DisplayDevMsg(const char* fmt, ...) = 0; + /** * @brief Attempts to notify the provider of the gamedll version being * used. diff --git a/core/provider/provider_base.cpp b/core/provider/provider_base.cpp index a33bd2c..1631bee 100644 --- a/core/provider/provider_base.cpp +++ b/core/provider/provider_base.cpp @@ -2,7 +2,7 @@ * vim: set ts=4 sw=4 tw=99 noet : * ====================================================== * Metamod:Source - * Copyright (C) 2004-2009 AlliedModders LLC and authors. + * Copyright (C) 2004-2023 AlliedModders LLC and authors. * All rights reserved. * ====================================================== * @@ -30,8 +30,6 @@ #include #include #include -#include -#include #include "../metamod_util.h" #include "provider_base.h" #include "console.h" @@ -39,31 +37,7 @@ #include #include "metamod.h" #include -#if SOURCE_ENGINE == SE_DOTA -#include -#endif -#if SOURCE_ENGINE == SE_DOTA && defined( _WIN32 ) -SH_DECL_HOOK1(ISource2ServerConfig, AllowDedicatedServers, const, 0, bool, EUniverse); -bool BaseProvider::AllowDedicatedServers(EUniverse universe) const -{ - RETURN_META_VALUE(MRES_SUPERCEDE, true); -} -#endif - -/* 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_ORANGEBOX @@ -71,239 +45,21 @@ struct UsrMsgInfo DLL_IMPORT ICommandLine *CommandLine(); #endif -/* Functions */ -void CacheUserMessages(); -bool KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID = NULL); -void Detour_Error(const tchar *pMsg, ...); - -#if SOURCE_ENGINE == SE_DOTA -void ClientCommand(CEntityIndex index, const CCommand &args); -#elif SOURCE_ENGINE >= SE_ORANGEBOX -void ClientCommand(edict_t *pEdict, const CCommand &args); -#else -void ClientCommand(edict_t *pEdict); -#endif - -#if SOURCE_ENGINE >= SE_ORANGEBOX -void LocalCommand_Meta(const CCommand &args); -#else -void LocalCommand_Meta(); -#endif - void _ServerCommand(); /* Variables */ -static BaseProvider g_Ep1Provider; static List conbases_unreg; -static CVector usermsgs_list; -static jmp_buf usermsg_end; -static bool g_bOriginalEngine = false; ICvar *icvar = NULL; -IFileSystem *baseFs = NULL; IServerGameDLL *server = NULL; -#if SOURCE_ENGINE == SE_DOTA -static ISource2ServerConfig *serverconfig = NULL; -INetworkServerService *netservice = NULL; -IEngineServiceMgr *enginesvcmgr = NULL; -#endif IVEngineServer *engine = NULL; IServerGameClients *gameclients = NULL; CGlobalVars *gpGlobals = NULL; -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) -{ -#if SOURCE_ENGINE == SE_SDK2013 - // Shim to avoid hooking shims - engine = (IVEngineServer *)((engineFactory)("VEngineServer023", NULL)); - if (!engine) - { - engine = (IVEngineServer *)((engineFactory)("VEngineServer022", NULL)); - if (!engine) - { - engine = (IVEngineServer *)((engineFactory)("VEngineServer021", NULL)); - } - } -#else - engine = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); -#endif - if (!engine) - { - DisplayError("Could not find IVEngineServer! Metamod cannot load."); - return; - } -#if SOURCE_ENGINE == SE_DOTA - gpGlobals = engine->GetServerGlobals(); - serverconfig = (ISource2ServerConfig *) ((serverFactory) (INTERFACEVERSION_SERVERCONFIG, NULL)); - netservice = (INetworkServerService *) ((engineFactory) (NETWORKSERVERSERVICE_INTERFACE_VERSION, NULL)); - enginesvcmgr = (IEngineServiceMgr *) ((engineFactory) (ENGINESERVICEMGR_INTERFACE_VERSION, NULL)); -#endif -#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 SOURCE_ENGINE == SE_DOTA - gameclients = (IServerGameClients *)(serverFactory(INTERFACEVERSION_SERVERGAMECLIENTS, NULL)); -#else - if ((gameclients = (IServerGameClients *)(serverFactory("ServerGameClients003", NULL))) - == NULL) - { - gameclients = (IServerGameClients *)(serverFactory("ServerGameClients004", NULL)); - } -#endif - - 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_DOTA && 0 - // Since we have to be added as a Game path (cannot add GameBin directly), we - // automatically get added to other paths as well, including having the MM:S - // dir become the default write path for logs and more. We can fix some of these. - - char searchPath[260]; - baseFs->GetSearchPath("GAME", (GetSearchPathTypes_t)0, searchPath, sizeof(searchPath)); - for (size_t i = 0; i < sizeof(searchPath); ++i) - { - if (searchPath[i] == ';') - { - searchPath[i] = '\0'; - break; - } - } - baseFs->RemoveSearchPath(searchPath, "GAME"); - - // TODO: figure out why these calls get ignored and path remains - //baseFs->RemoveSearchPath(searchPath, "CONTENT"); - //baseFs->RemoveSearchPath(searchPath, "SHADER_SOURCE"); - //baseFs->RemoveSearchPath(searchPath, "SHADER_SOURCE_MOD"); - - baseFs->RemoveSearchPaths("DEFAULT_WRITE_PATH"); - baseFs->GetSearchPath("GAME", (GetSearchPathTypes_t)0, searchPath, sizeof(searchPath)); - for (size_t i = 0; i < sizeof(searchPath); ++i) - { - if (searchPath[i] == ';') - { - searchPath[i] = '\0'; - break; - } - } - baseFs->AddSearchPath(searchPath, "DEFAULT_WRITE_PATH"); -#endif - -#if SOURCE_ENGINE >= SE_ORANGEBOX - g_pCVar = icvar; -#endif - - g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd); - -#if SOURCE_ENGINE == SE_EPISODEONE - /* The Ship is the only game known at this time that uses the pre-Episode One engine */ - g_bOriginalEngine = strcmp(CommandLine()->ParmValue("-game", "hl2"), "ship") == 0; -#endif - - CacheUserMessages(); - -#if SOURCE_ENGINE < SE_ORANGEBOX - 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); - } - -#if SOURCE_ENGINE == SE_DOTA && defined( _WIN32 ) - SH_ADD_VPHOOK(ISource2ServerConfig, AllowDedicatedServers, serverconfig, SH_MEMBER(this, &BaseProvider::AllowDedicatedServers), false); -#endif -} - -void BaseProvider::Notify_DLLShutdown_Pre() -{ - g_SMConVarAccessor.RemoveMetamodCommands(); - -#if SOURCE_ENGINE < SE_ORANGEBOX - 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 - gpGlobals->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 @@ -352,54 +108,6 @@ bool BaseProvider::LogMessage(const char *buffer) return true; } -bool BaseProvider::GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo) -{ -#if SOURCE_ENGINE == SE_DOTA - SourceHook::MemFuncInfo mfi = {true, -1, 0, 0}; - - switch (hook) - { - case ProvidedHook_StartupServer: - SourceHook::GetFuncInfo(&INetworkServerService::StartupServer, mfi); - break; - case ProvidedHook_StartChangeLevel: - SourceHook::GetFuncInfo(&INetworkGameServer::StartChangeLevel, mfi); - break; - case ProvidedHook_Init: - SourceHook::GetFuncInfo(&INetworkGameServer::Init, mfi); - break; - case ProvidedHook_SwitchToLoop: - SourceHook::GetFuncInfo(&IEngineServiceMgr::SwitchToLoop, mfi); - break; - default: - return false; - } - - *pInfo = mfi; - - return (mfi.thisptroffs >= 0); -#else - 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); -#endif -} - void BaseProvider::DisplayError(const char *fmt, ...) { va_list ap; @@ -424,444 +132,14 @@ void BaseProvider::DisplayWarning(const char *fmt, ...) Warning("%s", buffer); } -IConCommandBaseAccessor *BaseProvider::GetConCommandBaseAccessor() +void BaseProvider::DisplayDevMsg(const char* fmt, ...) { - 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 || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV - 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(); -} - -void BaseProvider::GetGamePath(char *pszBuffer, int len) -{ - engine->GetGameDir(pszBuffer, len); -} - -const char *BaseProvider::GetGameDescription() -{ -#if SOURCE_ENGINE == SE_DOTA - return serverconfig->GetGameDescription(); -#else - return server->GetGameDescription(); -#endif -} - -int BaseProvider::DetermineSourceEngine() -{ -#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_CONTAGION - return SOURCE_ENGINE_CONTAGION; -#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_DOI - return SOURCE_ENGINE_DOI; -#elif SOURCE_ENGINE == SE_CSGO - return SOURCE_ENGINE_CSGO; -#elif SOURCE_ENGINE == SE_DOTA - return SOURCE_ENGINE_DOTA; -#elif SOURCE_ENGINE == SE_BMS - return SOURCE_ENGINE_BMS; -#elif SOURCE_ENGINE == SE_EPISODEONE - return g_bOriginalEngine ? SOURCE_ENGINE_ORIGINAL : SOURCE_ENGINE_EPISODEONE; -#elif SOURCE_ENGINE == SE_MOCK - return SOURCE_ENGINE_MOCK; -#elif SOURCE_ENGINE == SE_PVKII - return SOURCE_ENGINE_PVKII; -#elif SOURCE_ENGINE == SE_MCV - return SOURCE_ENGINE_MCV; -#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; - bool bKVLoaded = false; - const char *plugin_file, *p_alias; - - pValues = new KeyValues("Metamod Plugin"); - - if (g_bOriginalEngine) - { - /* The Ship must use a special version of this function */ - bKVLoaded = KVLoadFromFile(pValues, baseFs, file); - } - else - { - bKVLoaded = pValues->LoadFromFile(baseFs, file); - } - - if (!bKVLoaded) - { - 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; -} - -const char *BaseProvider::GetEngineDescription() const -{ -#if SOURCE_ENGINE == SE_BLOODYGOODTIME - return "Bloody Good Time (2010)"; -#elif SOURCE_ENGINE == SE_ALIENSWARM - return "Alien Swarm (2010)"; -#elif SOURCE_ENGINE == SE_LEFT4DEAD2 - return "Left 4 Dead 2 (2009)"; -#elif SOURCE_ENGINE == SE_NUCLEARDAWN - return "Nuclear Dawn (2011)"; -#elif SOURCE_ENGINE == SE_CONTAGION - return "Contagion (2013)"; -#elif SOURCE_ENGINE == SE_LEFT4DEAD - return "Left 4 Dead (2008)"; -#elif SOURCE_ENGINE == SE_ORANGEBOX - return "Episode 2 (Orange Box, 2007)"; -#elif SOURCE_ENGINE == SE_CSS - return "Counter-Strike: Source (Valve Orange Box)"; -#elif SOURCE_ENGINE == SE_HL2DM - return "Half-Life 2 Deathmatch (Valve Orange Box)"; -#elif SOURCE_ENGINE == SE_DODS - return "Day of Defeat: Source (Valve Orange Box)"; -#elif SOURCE_ENGINE == SE_SDK2013 - return "Source SDK 2013 (2013)"; -#elif SOURCE_ENGINE == SE_BMS - return "Black Mesa (2015)"; -#elif SOURCE_ENGINE == SE_TF2 - return "Team Fortress 2 (Valve Orange Box)"; -#elif SOURCE_ENGINE == SE_DARKMESSIAH - return "Dark Messiah (2006)"; -#elif SOURCE_ENGINE == SE_EYE - return "E.Y.E. Divine Cybermancy (2011)"; -#elif SOURCE_ENGINE == SE_PORTAL2 - return "Portal 2 (2011)"; -#elif SOURCE_ENGINE == SE_BLADE - return "Blade Symphony (2013)"; -#elif SOURCE_ENGINE == SE_INSURGENCY - return "Insurgency (2013)"; -#elif SOURCE_ENGINE == SE_DOI - return "Day of Infamy (2016)"; -#elif SOURCE_ENGINE == SE_CSGO - return "Counter-Strike: Global Offensive (2012)"; -#elif SOURCE_ENGINE == SE_DOTA - return "Dota 2 (2013)"; -#elif SOURCE_ENGINE == SE_EPISODEONE - if (g_bOriginalEngine) - { - return "Original (pre-Episode 1)"; - } - else - { - return "Episode 1 (2004)"; - } -#elif SOURCE_ENGINE == SE_MOCK - return "Mock"; -#elif SOURCE_ENGINE == SE_PVKII - return "Pirates, Vikings, and Knights II"; -#elif SOURCE_ENGINE == SE_MCV - return "Military Combat: Vietnam"; -#else -#error "SOURCE_ENGINE not defined to a known value" -#endif -} - -#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_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 || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV - -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 - -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; + va_list ap; + char buffer[2048]; + + va_start(ap, fmt); + UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + DevMsg("%s", buffer); } diff --git a/core/provider/provider_base.h b/core/provider/provider_base.h index d888c93..c57c8f9 100644 --- a/core/provider/provider_base.h +++ b/core/provider/provider_base.h @@ -50,41 +50,47 @@ class INetworkGameServer; class BaseProvider : public IMetamodSourceProvider { -public: +public: // Must implement + virtual void Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, CreateInterfaceFn serverFactory) override = 0; + virtual void Notify_DLLShutdown_Pre() override = 0; + virtual int DetermineSourceEngine() override = 0; + virtual const char *GetEngineDescription() const override = 0; + virtual void GetGamePath(char *pszBuffer, int len) override = 0; + virtual const char *GetGameDescription() override = 0; + virtual bool ProcessVDF(const char* file, char path[], size_t path_len, char alias[], size_t alias_len) override = 0; + virtual void ConsolePrint(const char* msg) override = 0; + virtual void ClientConsolePrint(edict_t* client, const char* msg) override = 0; + virtual void ServerCommand(const char* cmd) override = 0; + virtual ConVar* CreateConVar(const char* name, + const char* defval, + const char* help, + int flags) override = 0; + virtual const char* GetConVarString(ConVar* convar) override = 0; + virtual void SetConVarString(ConVar* convar, const char* str) override = 0; + virtual IConCommandBaseAccessor* GetConCommandBaseAccessor() override = 0; + virtual bool RegisterConCommandBase(ConCommandBase* pCommand) override = 0; + virtual void UnregisterConCommandBase(ConCommandBase* pCommand) override = 0; + virtual bool IsConCommandBaseACommand(ConCommandBase* pCommand) override = 0; +public: // May implement/override (stubbed) + virtual int GetUserMessageCount() override { return -1; } + virtual int FindUserMessage(const char *name, int *size=nullptr) override { return -1;} + virtual const char *GetUserMessage(int index, int *size=nullptr) override { return nullptr;} +public: // May implement/override virtual bool IsSourceEngineBuildCompatible(int build) override; - virtual bool GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo) override; virtual bool LogMessage(const char *buffer) override; virtual const char *GetCommandLineValue(const char *key, const char *defval) override; - virtual void ConsolePrint(const char *msg) override; virtual bool IsRemotePrintingAvailable() override; - virtual void ClientConsolePrint(edict_t *client, const char *msg) override; virtual void DisplayError(const char *fmt, ...) override; virtual void DisplayWarning(const char *fmt, ...) override; + virtual void DisplayDevMsg(const char* fmt, ...) override; virtual int TryServerGameDLL(const char *iface) override; - virtual void Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, CreateInterfaceFn serverFactory) override; - void Notify_DLLShutdown_Pre() override; - virtual void ServerCommand(const char *cmd) override; - virtual ConVar *CreateConVar(const char *name, - const char *defval, - const char *help, - int flags) override; - virtual const char *GetConVarString(ConVar *convar) override; - virtual void SetConVarString(ConVar *convar, const char *str) override; - virtual void GetGamePath(char *pszBuffer, int len) override; - virtual const char *GetGameDescription() override; - virtual IConCommandBaseAccessor *GetConCommandBaseAccessor() override; - virtual bool RegisterConCommandBase(ConCommandBase *pCommand) override; - virtual void UnregisterConCommandBase(ConCommandBase *pCommand) override; - virtual bool IsConCommandBaseACommand(ConCommandBase *pCommand) override; - virtual int GetUserMessageCount() override; - virtual int FindUserMessage(const char *name, int *size=NULL) override; - virtual const char *GetUserMessage(int index, int *size=NULL) override; - virtual int DetermineSourceEngine() override; - virtual bool ProcessVDF(const char *file, char path[], size_t path_len, char alias[], size_t alias_len) override; - virtual const char *GetEngineDescription() const override; -#if SOURCE_ENGINE == SE_DOTA && defined( _WIN32 ) - bool AllowDedicatedServers(EUniverse universe) const; -#endif +public: + void SetCallbacks(IMetamodSourceProviderCallbacks* pCallbacks) override final + { + m_pCallbacks = pCallbacks; + } +protected: + IMetamodSourceProviderCallbacks* m_pCallbacks = nullptr; }; extern IVEngineServer *engine; diff --git a/core/provider/source/provider_source.cpp b/core/provider/source/provider_source.cpp index 8a2d0db..a376caa 100644 --- a/core/provider/source/provider_source.cpp +++ b/core/provider/source/provider_source.cpp @@ -24,6 +24,625 @@ */ #include "provider_source.h" +#include "../console.h" +#include +#include +#include +#include +#include + +#if SOURCE_ENGINE >= SE_ORANGEBOX +void LocalCommand_Meta(const CCommand& args); +#else +void LocalCommand_Meta(); +#endif + +ConCommand meta_local_cmd("meta", LocalCommand_Meta, "Metamod:Source control options"); + +SH_DECL_HOOK0(IServerGameDLL, GameInit, SH_NOATTRIB, 0, bool); +SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, 0, bool, const char*, const char*, const char*, const char*, bool, bool); +SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, 0); + +#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 SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, + CreateInterfaceFn serverFactory) +{ +#if SOURCE_ENGINE == SE_SDK2013 + // Shim to avoid hooking shims + engine = (IVEngineServer*)((engineFactory)("VEngineServer023", NULL)); + if (!engine) + { + engine = (IVEngineServer*)((engineFactory)("VEngineServer022", NULL)); + if (!engine) + { + engine = (IVEngineServer*)((engineFactory)("VEngineServer021", NULL)); + } + } +#else + engine = (IVEngineServer*)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); +#endif + 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); + +#if SOURCE_ENGINE == SE_EPISODEONE + /* The Ship is the only game known at this time that uses the pre-Episode One engine */ + bOriginalEngine = strcmp(CommandLine()->ParmValue("-game", "hl2"), "ship") == 0; +#endif + + CacheUserMessages(); + +#if SOURCE_ENGINE < SE_ORANGEBOX + 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(IServerGameClients, ClientCommand, gameclients, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand), false); + } + + SH_ADD_HOOK(IServerGameDLL, GameInit, server, SH_MEMBER(this, &SourceProvider::Hook_GameInit), false); + SH_ADD_HOOK(IServerGameDLL, LevelInit, server, SH_MEMBER(this, &SourceProvider::Hook_LevelInit), true); + SH_ADD_HOOK(IServerGameDLL, LevelShutdown, server, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown), true); +} + +void SourceProvider::Notify_DLLShutdown_Pre() +{ + SH_REMOVE_HOOK(IServerGameDLL, GameInit, server, SH_MEMBER(this, &SourceProvider::Hook_GameInit), false); + SH_REMOVE_HOOK(IServerGameDLL, LevelInit, server, SH_MEMBER(this, &SourceProvider::Hook_LevelInit), true); + SH_REMOVE_HOOK(IServerGameDLL, LevelShutdown, server, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown), true); + + g_SMConVarAccessor.RemoveMetamodCommands(); + +#if SOURCE_ENGINE < SE_ORANGEBOX + if (g_Metamod.IsLoadedAsGameDLL()) + { + icvar->UnlinkVariables(FCVAR_GAMEDLL); + } +#endif +} + +bool SourceProvider::ProcessVDF(const char* file, char path[], size_t path_len, char alias[], size_t alias_len) +{ + if (baseFs == NULL) + { + return false; + } + + KeyValues* pValues; + bool bKVLoaded = false; + const char* plugin_file, * p_alias; + + pValues = new KeyValues("Metamod Plugin"); + + if (bOriginalEngine) + { + /* The Ship must use a special version of this function */ + bKVLoaded = KVLoadFromFile(pValues, baseFs, file); + } + else + { + bKVLoaded = pValues->LoadFromFile(baseFs, file); + } + + if (!bKVLoaded) + { + 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; +} + +int SourceProvider::DetermineSourceEngine() +{ +#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_CONTAGION + return SOURCE_ENGINE_CONTAGION; +#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_DOI + return SOURCE_ENGINE_DOI; +#elif SOURCE_ENGINE == SE_CSGO + return SOURCE_ENGINE_CSGO; +#elif SOURCE_ENGINE == SE_BMS + return SOURCE_ENGINE_BMS; +#elif SOURCE_ENGINE == SE_EPISODEONE + return g_bOriginalEngine ? SOURCE_ENGINE_ORIGINAL : SOURCE_ENGINE_EPISODEONE; +#elif SOURCE_ENGINE == SE_MOCK + return SOURCE_ENGINE_MOCK; +#elif SOURCE_ENGINE == SE_PVKII + return SOURCE_ENGINE_PVKII; +#elif SOURCE_ENGINE == SE_MCV + return SOURCE_ENGINE_MCV; +#else +#error "SOURCE_ENGINE not defined to a known value" +#endif +} + +const char* SourceProvider::GetEngineDescription() const +{ +#if SOURCE_ENGINE == SE_BLOODYGOODTIME + return "Bloody Good Time (2010)"; +#elif SOURCE_ENGINE == SE_ALIENSWARM + return "Alien Swarm (2010)"; +#elif SOURCE_ENGINE == SE_LEFT4DEAD2 + return "Left 4 Dead 2 (2009)"; +#elif SOURCE_ENGINE == SE_NUCLEARDAWN + return "Nuclear Dawn (2011)"; +#elif SOURCE_ENGINE == SE_CONTAGION + return "Contagion (2013)"; +#elif SOURCE_ENGINE == SE_LEFT4DEAD + return "Left 4 Dead (2008)"; +#elif SOURCE_ENGINE == SE_ORANGEBOX + return "Episode 2 (Orange Box, 2007)"; +#elif SOURCE_ENGINE == SE_CSS + return "Counter-Strike: Source (Valve Orange Box)"; +#elif SOURCE_ENGINE == SE_HL2DM + return "Half-Life 2 Deathmatch (Valve Orange Box)"; +#elif SOURCE_ENGINE == SE_DODS + return "Day of Defeat: Source (Valve Orange Box)"; +#elif SOURCE_ENGINE == SE_SDK2013 + return "Source SDK 2013 (2013)"; +#elif SOURCE_ENGINE == SE_BMS + return "Black Mesa (2015)"; +#elif SOURCE_ENGINE == SE_TF2 + return "Team Fortress 2 (Valve Orange Box)"; +#elif SOURCE_ENGINE == SE_DARKMESSIAH + return "Dark Messiah (2006)"; +#elif SOURCE_ENGINE == SE_EYE + return "E.Y.E. Divine Cybermancy (2011)"; +#elif SOURCE_ENGINE == SE_PORTAL2 + return "Portal 2 (2011)"; +#elif SOURCE_ENGINE == SE_BLADE + return "Blade Symphony (2013)"; +#elif SOURCE_ENGINE == SE_INSURGENCY + return "Insurgency (2013)"; +#elif SOURCE_ENGINE == SE_DOI + return "Day of Infamy (2016)"; +#elif SOURCE_ENGINE == SE_CSGO + return "Counter-Strike: Global Offensive (2012)"; +#elif SOURCE_ENGINE == SE_EPISODEONE + if (g_bOriginalEngine) + { + return "Original (pre-Episode 1)"; + } + else + { + return "Episode 1 (2004)"; + } +#elif SOURCE_ENGINE == SE_MOCK + return "Mock"; +#elif SOURCE_ENGINE == SE_PVKII + return "Pirates, Vikings, and Knights II"; +#elif SOURCE_ENGINE == SE_MCV + return "Military Combat: Vietnam"; +#else +#error "SOURCE_ENGINE not defined to a known value" +#endif +} + +void SourceProvider::GetGamePath(char* pszBuffer, int len) +{ + engine->GetGameDir(pszBuffer, len); +} + +const char* SourceProvider::GetGameDescription() +{ + return server->GetGameDescription(); +} + +void SourceProvider::ConsolePrint(const char* str) +{ +#if SOURCE_ENGINE >= SE_ORANGEBOX + ConMsg("%s", str); +#else + Msg("%s", str); +#endif +} + +void SourceProvider::ClientConsolePrint(edict_t* pEdict, const char* message) +{ + engine->ClientPrintf(pEdict, message); +} + +void SourceProvider::ServerCommand(const char* cmd) +{ + engine->ServerCommand(cmd); +} + +const char* SourceProvider::GetConVarString(ConVar* convar) +{ + if (convar == NULL) + { + return NULL; + } + + return convar->GetString(); +} + +void SourceProvider::SetConVarString(ConVar* convar, const char* str) +{ + convar->SetValue(str); +} + +bool SourceProvider::IsConCommandBaseACommand(ConCommandBase* pCommand) +{ + return pCommand->IsCommand(); +} + +IConCommandBaseAccessor* SourceProvider::GetConCommandBaseAccessor() +{ + return &g_SMConVarAccessor; +} + +bool SourceProvider::RegisterConCommandBase(ConCommandBase* pCommand) +{ + return g_SMConVarAccessor.Register(pCommand); +} + +void SourceProvider::UnregisterConCommandBase(ConCommandBase* pCommand) +{ + return g_SMConVarAccessor.Unregister(pCommand); +} + +ConVar* SourceProvider::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; +} + +#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_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV + +void SourceProvider::CacheUserMessages() +{ +} + +#else +static jmp_buf usermsg_end; + +/* This only gets called if IServerGameDLL::GetUserMessageInfo() triggers it */ +void SourceProvider::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 SourceProvider::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 + +int SourceProvider::GetUserMessageCount() +{ + if (!IsUserMessageIterationSupported()) + return -1; + + return (int)usermsgs_list.size(); +} + +int SourceProvider::FindUserMessage(const char* name, int* size) +{ + if (IsUserMessageIterationSupported()) + { + 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* SourceProvider::GetUserMessage(int index, int* size) +{ + if (!IsUserMessageIterationSupported() || index < 0 || index >= (int)usermsgs_list.size()) + { + return nullptr; + } + + if (size) + { + *size = usermsgs_list[index].size; + } + + return usermsgs_list[index].name.c_str(); +} + +bool SourceProvider::KVLoadFromFile(KeyValues* kv, IFileSystem* 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; +} + +bool SourceProvider::Hook_GameInit() +{ + if (nullptr != m_pCallbacks) + { + m_pCallbacks->OnGameInit(); + } + + RETURN_META_VALUE(MRES_IGNORED, true); +} + +bool SourceProvider::Hook_LevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, + char const* pLandmarkName, bool loadGame, bool background) +{ + if (nullptr != m_pCallbacks) + { + m_pCallbacks->OnLevelInit(pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background); + } + + RETURN_META_VALUE(MRES_IGNORED, true); +} + +void SourceProvider::Hook_LevelShutdown() +{ + if (nullptr != m_pCallbacks) + { + m_pCallbacks->OnLevelShutdown(); + } + + RETURN_META(MRES_IGNORED); +} + +#if SOURCE_ENGINE >= SE_ORANGEBOX +void SourceProvider::Hook_ClientCommand(edict_t* client, const CCommand& _cmd) +{ + GlobCommand cmd(&_cmd); +#else +void SourceProvider::Hook_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); +} static SourceProvider g_SourceProvider; diff --git a/core/provider/source/provider_source.h b/core/provider/source/provider_source.h index 6ce682e..267e8fd 100644 --- a/core/provider/source/provider_source.h +++ b/core/provider/source/provider_source.h @@ -29,10 +29,73 @@ #define _INCLUDE_METAMOD_SOURCE_SOURCE1_PROVIDER_H_ #include "../provider_base.h" +#include +#include class SourceProvider : public BaseProvider { - +public: + virtual void Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, CreateInterfaceFn serverFactory) override; + virtual void Notify_DLLShutdown_Pre() override; + virtual bool ProcessVDF(const char* file, char path[], size_t path_len, char alias[], size_t alias_len) override; + virtual int DetermineSourceEngine() override; + virtual const char* GetEngineDescription() const override; + virtual void GetGamePath(char* pszBuffer, int len) override; + virtual const char* GetGameDescription() override; + virtual void ConsolePrint(const char* msg) override; + virtual void ClientConsolePrint(edict_t* client, const char* msg) override; + virtual void ServerCommand(const char* cmd) override; + virtual ConVar* CreateConVar(const char* name, + const char* defval, + const char* help, + int flags) override; + virtual const char* GetConVarString(ConVar* convar) override; + virtual void SetConVarString(ConVar* convar, const char* str) override; + virtual IConCommandBaseAccessor* GetConCommandBaseAccessor() override; + virtual bool RegisterConCommandBase(ConCommandBase* pCommand) override; + virtual void UnregisterConCommandBase(ConCommandBase* pCommand) override; + virtual bool IsConCommandBaseACommand(ConCommandBase* pCommand) override; + virtual int GetUserMessageCount() override; + virtual int FindUserMessage(const char* name, int* size = nullptr) override; + virtual const char* GetUserMessage(int index, int* size = nullptr) override; +public: // Hook callbacks that map to provider callbacks + bool Hook_GameInit(); + bool Hook_LevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, + char const* pLandmarkName, bool loadGame, bool background); + void Hook_LevelShutdown(); +#if SOURCE_ENGINE >= SE_ORANGEBOX + void Hook_ClientCommand(edict_t* pEdict, const CCommand& args); +#else + void Hook_ClientCommand(edict_t* pEdict); +#endif +private: + struct UsrMsgInfo + { + UsrMsgInfo() + { + } + UsrMsgInfo(int s, const char* t) : size(s), name(t) + { + } + int size; + std::string name; + }; +private: + void CacheUserMessages(); + void Detour_Error(const tchar* pMsg, ...); + bool KVLoadFromFile(KeyValues* kv, IFileSystem* filesystem, const char* resourceName, const char* pathID = nullptr); + inline bool IsUserMessageIterationSupported() const + { +#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV + return false; +#else + return true; +#endif + } +private: + IFileSystem* baseFs = nullptr; + std::vector usermsgs_list; + bool bOriginalEngine = false; }; #endif diff --git a/core/provider/source2/provider_source2.cpp b/core/provider/source2/provider_source2.cpp index 237868d..88d88fb 100644 --- a/core/provider/source2/provider_source2.cpp +++ b/core/provider/source2/provider_source2.cpp @@ -24,6 +24,401 @@ */ #include "provider_source2.h" +#include "../console.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void LocalCommand_Meta(const CCommand& args); + +ConCommand meta_local_cmd("meta", LocalCommand_Meta, "Metamod:Source control options"); + +static ISource2ServerConfig* serverconfig = NULL; +INetworkServerService* netservice = NULL; +IEngineServiceMgr* enginesvcmgr = NULL; + +// Hack to make hook decl compile when only having forward decl in header. +// (we have class structure but it requires protobuf which we don't want to include here) +class GameSessionConfiguration_t { }; + +SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t &, ISource2WorldSession *, const char *); +SH_DECL_HOOK5_void(IEngineServiceMgr, SwitchToLoop, SH_NOATTRIB, 0, const char *, KeyValues *, uint32, const char *, bool); +SH_DECL_HOOK2_void(INetworkGameServer, Init, SH_NOATTRIB, 0, const GameSessionConfiguration_t &, const char *); +SH_DECL_HOOK3(INetworkGameServer, StartChangeLevel, SH_NOATTRIB, 0, CUtlVector *, const char *, const char *, void *); +SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, CEntityIndex, const CCommand&); + +#ifdef SHOULD_OVERRIDE_ALLOWDEDICATED_SERVER +SH_DECL_HOOK1(ISource2ServerConfig, AllowDedicatedServers, const, 0, bool, EUniverse); +#endif + +void Source2Provider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, + CreateInterfaceFn serverFactory) +{ + engine = (IVEngineServer*)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); + if (!engine) + { + DisplayError("Could not find IVEngineServer! Metamod cannot load."); + return; + } + + gpGlobals = engine->GetServerGlobals(); + serverconfig = (ISource2ServerConfig*)((serverFactory)(INTERFACEVERSION_SERVERCONFIG, NULL)); + netservice = (INetworkServerService*)((engineFactory)(NETWORKSERVERSERVICE_INTERFACE_VERSION, NULL)); + enginesvcmgr = (IEngineServiceMgr*)((engineFactory)(ENGINESERVICEMGR_INTERFACE_VERSION, NULL)); + + icvar = (ICvar*)((engineFactory)(CVAR_INTERFACE_VERSION, NULL)); + if (!icvar) + { + DisplayError("Could not find ICvar! Metamod cannot load."); + return; + } + + gameclients = (IServerGameClients*)(serverFactory(INTERFACEVERSION_SERVERGAMECLIENTS, 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 0 + // Since we have to be added as a Game path (cannot add GameBin directly), we + // automatically get added to other paths as well, including having the MM:S + // dir become the default write path for logs and more. We can fix some of these. + + char searchPath[260]; + baseFs->GetSearchPath("GAME", (GetSearchPathTypes_t)0, searchPath, sizeof(searchPath)); + for (size_t i = 0; i < sizeof(searchPath); ++i) + { + if (searchPath[i] == ';') + { + searchPath[i] = '\0'; + break; + } + } + baseFs->RemoveSearchPath(searchPath, "GAME"); + + // TODO: figure out why these calls get ignored and path remains + //baseFs->RemoveSearchPath(searchPath, "CONTENT"); + //baseFs->RemoveSearchPath(searchPath, "SHADER_SOURCE"); + //baseFs->RemoveSearchPath(searchPath, "SHADER_SOURCE_MOD"); + + baseFs->RemoveSearchPaths("DEFAULT_WRITE_PATH"); + baseFs->GetSearchPath("GAME", (GetSearchPathTypes_t)0, searchPath, sizeof(searchPath)); + for (size_t i = 0; i < sizeof(searchPath); ++i) + { + if (searchPath[i] == ';') + { + searchPath[i] = '\0'; + break; + } + } + baseFs->AddSearchPath(searchPath, "DEFAULT_WRITE_PATH"); +#endif + + g_pCVar = icvar; + + g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd); + + if (gameclients) + { + SH_ADD_HOOK(IServerGameClients, ClientCommand, gameclients, SH_MEMBER(this, &Source2Provider::Hook_ClientCommand), false); + } + +#ifdef SHOULD_OVERRIDE_ALLOWDEDICATED_SERVER + SH_ADD_VPHOOK(ISource2ServerConfig, AllowDedicatedServers, serverconfig, SH_MEMBER(this, &Source2Provider::Hook_AllowDedicatedServers), false); +#endif + + SH_ADD_HOOK(INetworkServerService, StartupServer, netservice, SH_MEMBER(this, &Source2Provider::Hook_StartupServer_Post), true); + SH_ADD_HOOK(IEngineServiceMgr, SwitchToLoop, enginesvcmgr, SH_MEMBER(this, &Source2Provider::Hook_SwitchToLoop), false); +} + +void Source2Provider::Notify_DLLShutdown_Pre() +{ + g_SMConVarAccessor.RemoveMetamodCommands(); +} + +bool Source2Provider::ProcessVDF(const char* file, char path[], size_t path_len, char alias[], size_t alias_len) +{ + if (baseFs == NULL) + { + return false; + } + + KeyValues* pValues; + bool bKVLoaded = false; + const char* plugin_file, * p_alias; + + pValues = new KeyValues("Metamod Plugin"); + + bKVLoaded = pValues->LoadFromFile(baseFs, file); + if (!bKVLoaded) + { + delete pValues; + return false; + } + + if ((plugin_file = pValues->GetString("file", NULL)) == NULL) + { + delete pValues; + 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, ""); + } + + delete pValues; + + return true; +} + +int Source2Provider::DetermineSourceEngine() +{ +#if SOURCE_ENGINE == SE_DOTA + return SOURCE_ENGINE_DOTA; +#elif SOURCE_ENGINE == SE_CS2 + return SOURCE_ENGINE_CS2; +#else +#error "SOURCE_ENGINE not defined to a known value" +#endif +} + +const char* Source2Provider::GetEngineDescription() const +{ +#if SOURCE_ENGINE == SE_DOTA + return "Dota 2 (2013)"; +#elif SOURCE_ENGINE == SE_CS2 + return "Counter-Strike 2 (2023)"; +#else +#error "SOURCE_ENGINE not defined to a known value" +#endif +} + +void Source2Provider::GetGamePath(char* pszBuffer, int len) +{ + ke::SafeSprintf(pszBuffer, len, "%s", Plat_GetGameDirectory()); +} + +const char* Source2Provider::GetGameDescription() +{ + return serverconfig->GetGameDescription(); +} + +#ifdef SHOULD_OVERRIDE_ALLOWDEDICATED_SERVER +bool Source2Provider::Hook_AllowDedicatedServers(EUniverse universe) const +{ + RETURN_META_VALUE(MRES_SUPERCEDE, true); +} +#endif + +void Source2Provider::ConsolePrint(const char* str) +{ + ConMsg("%s", str); +} + +void Source2Provider::ClientConsolePrint(edict_t* pEdict, const char* message) +{ + int client = (int)(pEdict - gpGlobals->pEdicts); + engine->ClientPrintf(client, message); +} + +void Source2Provider::ServerCommand(const char* cmd) +{ + engine->ServerCommand(cmd); +} + +const char* Source2Provider::GetConVarString(ConVar* convar) +{ + if (convar == NULL) + { + return NULL; + } + + return convar->GetString(); +} + +void Source2Provider::SetConVarString(ConVar* convar, const char* str) +{ + convar->SetValue(str); +} + +bool Source2Provider::IsConCommandBaseACommand(ConCommandBase* pCommand) +{ + return pCommand->IsCommand(); +} + +IConCommandBaseAccessor* Source2Provider::GetConCommandBaseAccessor() +{ + return &g_SMConVarAccessor; +} + +bool Source2Provider::RegisterConCommandBase(ConCommandBase* pCommand) +{ + return g_SMConVarAccessor.Register(pCommand); +} + +void Source2Provider::UnregisterConCommandBase(ConCommandBase* pCommand) +{ + return g_SMConVarAccessor.Unregister(pCommand); +} + +ConVar* Source2Provider::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; +} + +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; +}; + +void LocalCommand_Meta(const CCommand& args) +{ + GlobCommand cmd(&args); + Command_Meta(&cmd); +} + +bool Source2Provider::KVLoadFromFile(KeyValues* kv, IFileSystem* 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; +} + +void Source2Provider::Hook_StartupServer_Post(const GameSessionConfiguration_t &config, ISource2WorldSession *, const char *) +{ + static bool bGameServerHooked = false; + if (!bGameServerHooked) + { + INetworkGameServer* netserver = (META_IFACEPTR(INetworkServerService))->GetIGameServer(); + + SH_ADD_VPHOOK(INetworkGameServer, Init, netserver, SH_MEMBER(this, &Source2Provider::Hook_Init), false); + SH_ADD_VPHOOK(INetworkGameServer, StartChangeLevel, netserver, SH_MEMBER(this, &Source2Provider::Hook_StartChangeLevel), false); + + bGameServerHooked = true; + } + + RETURN_META(MRES_IGNORED); +} + +void Source2Provider::Hook_Init(const GameSessionConfiguration_t &config, const char *pszMapName) +{ + static char szLastMap[260] = ""; + if (nullptr != m_pCallbacks) + { + m_pCallbacks->OnLevelInit(pszMapName, "", sLastMap.c_str(), "", false, false); + } + + sLastMap = pszMapName; + + RETURN_META(MRES_IGNORED); +} + +CUtlVector *Source2Provider::Hook_StartChangeLevel(const char *, const char *, void *) +{ + if (nullptr != m_pCallbacks) + { + m_pCallbacks->OnLevelShutdown(); + } + + RETURN_META_VALUE(MRES_IGNORED, nullptr); +} + +void Source2Provider::Hook_SwitchToLoop(const char *pszLoopName, KeyValues *pKV, uint32 nId, const char *pszUnk, bool bUnk) +{ + if (nullptr != m_pCallbacks && strcmp(pszLoopName, "levelload") == 0) + { + m_pCallbacks->OnGameInit(); + } + + RETURN_META(MRES_IGNORED); +} + +void Source2Provider::Hook_ClientCommand(CEntityIndex index, const CCommand& _cmd) +{ + int client = index.Get(); + GlobCommand cmd(&_cmd); + + if (strcmp(cmd.GetArg(0), "meta") == 0) + { + Command_ClientMeta(client, &cmd); + RETURN_META(MRES_SUPERCEDE); + } + + RETURN_META(MRES_IGNORED); +} static Source2Provider g_Source2Provider; diff --git a/core/provider/source2/provider_source2.h b/core/provider/source2/provider_source2.h index f623762..da89353 100644 --- a/core/provider/source2/provider_source2.h +++ b/core/provider/source2/provider_source2.h @@ -29,10 +29,54 @@ #define _INCLUDE_METAMOD_SOURCE_SOURCE2_PROVIDER_H_ #include "../provider_base.h" +#include +#include + +// TODO: is this still needed for Dota or CS2 on any platform? +#if SOURCE_ENGINE == SE_DOTA && defined( _WIN32 ) +#define SHOULD_OVERRIDE_ALLOWDEDICATED_SERVER +#endif + +class INetworkGameClient; +class ISource2WorldSession; class Source2Provider : public BaseProvider { - +public: + virtual void Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, CreateInterfaceFn serverFactory) override; + virtual void Notify_DLLShutdown_Pre() override; + virtual bool ProcessVDF(const char* file, char path[], size_t path_len, char alias[], size_t alias_len) override; + virtual int DetermineSourceEngine() override; + virtual const char* GetEngineDescription() const override; + virtual void GetGamePath(char* pszBuffer, int len) override; + virtual const char* GetGameDescription() override; + virtual void ConsolePrint(const char* msg) override; + virtual void ClientConsolePrint(edict_t* client, const char* msg) override; + virtual void ServerCommand(const char* cmd) override; + virtual ConVar* CreateConVar(const char* name, + const char* defval, + const char* help, + int flags) override; + virtual const char* GetConVarString(ConVar* convar) override; + virtual void SetConVarString(ConVar* convar, const char* str) override; + virtual IConCommandBaseAccessor* GetConCommandBaseAccessor() override; + virtual bool RegisterConCommandBase(ConCommandBase* pCommand) override; + virtual void UnregisterConCommandBase(ConCommandBase* pCommand) override; + virtual bool IsConCommandBaseACommand(ConCommandBase* pCommand) override; +public: +#ifdef SHOULD_OVERRIDE_ALLOWDEDICATED_SERVER + bool Hook_AllowDedicatedServers(EUniverse universe) const; +#endif + void Hook_StartupServer_Post(const GameSessionConfiguration_t &config, ISource2WorldSession *, const char *); + void Hook_Init(const GameSessionConfiguration_t &config, const char* pszMapName); + CUtlVector *Hook_StartChangeLevel(const char*, const char*, void*); + void Hook_SwitchToLoop(const char *pszLoopName, KeyValues *pKV, uint32 nId, const char *pszUnk, bool bUnk); + void Hook_ClientCommand(CEntityIndex index, const CCommand& args); +private: + bool KVLoadFromFile(KeyValues *kv, IFileSystem *filesystem, const char *resourceName, const char *pathID); +private: + IFileSystem* baseFs = nullptr; + std::string sLastMap; }; #endif