1
0
mirror of https://github.com/alliedmodders/metamod-source.git synced 2025-01-19 08:52:34 +01:00

Round 1 of base provider split. (Only compilation has been tested)

This commit is contained in:
Nick Hastings 2023-04-01 15:07:59 -04:00
parent 3704e2f3c1
commit e5f5a8ce6f
8 changed files with 1278 additions and 1026 deletions

View File

@ -35,9 +35,6 @@
#include "metamod_console.h" #include "metamod_console.h"
#include "provider/provider_base.h" #include "provider/provider_base.h"
#include <sys/stat.h> #include <sys/stat.h>
#if SOURCE_ENGINE == SE_DOTA
#include <iserver.h>
#endif
#define X64_SUFFIX ".x64" #define X64_SUFFIX ".x64"
@ -50,50 +47,12 @@ using namespace SourceHook::Impl;
* @file sourcemm.cpp * @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<INetworkGameClient *> *, 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<INetworkGameClient *> *
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 static void
InitializeVSP(); InitializeVSP();
static void
DoInitialPluginLoads();
static int static int
LoadPluginsFromFile(const char *filepath, int &skipped); 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 */ /* Initialize everything here */
void void
mm_InitializeForLoad() mm_InitializeForLoad()
@ -184,46 +210,7 @@ mm_InitializeForLoad()
*/ */
in_first_level = true; in_first_level = true;
#if SOURCE_ENGINE == SE_DOTA provider->SetCallbacks(&s_ProviderCallbacks);
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
} }
bool bool
@ -531,174 +518,6 @@ mm_UnloadMetamod()
g_SourceHook.CompleteShutdown(); 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 <utlbuffer.h>
#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<INetworkGameClient *> *
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, ...) void MetamodSource::LogMsg(ISmmPlugin *pl, const char *msg, ...)
{ {
va_list ap; va_list ap;

View File

@ -49,6 +49,29 @@ namespace SourceMM
#endif #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 * @brief Abstracts command information, since the new engine fixes the
* re-entrancy problems in the tokenization system. * re-entrancy problems in the tokenization system.
@ -83,6 +106,13 @@ namespace SourceMM
class IMetamodSourceProvider class IMetamodSourceProvider
{ {
public: 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. * @brief Returns whether source engine build is compatible.
* *
@ -91,18 +121,6 @@ namespace SourceMM
*/ */
virtual bool IsSourceEngineBuildCompatible(int build) =0; 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. * @brief Logs a message via IVEngineServer::LogPrint.
* *
@ -167,6 +185,16 @@ namespace SourceMM
*/ */
virtual void DisplayWarning(const char *fmt, ...) =0; 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 * @brief Attempts to notify the provider of the gamedll version being
* used. * used.

View File

@ -2,7 +2,7 @@
* vim: set ts=4 sw=4 tw=99 noet : * vim: set ts=4 sw=4 tw=99 noet :
* ====================================================== * ======================================================
* Metamod:Source * Metamod:Source
* Copyright (C) 2004-2009 AlliedModders LLC and authors. * Copyright (C) 2004-2023 AlliedModders LLC and authors.
* All rights reserved. * All rights reserved.
* ====================================================== * ======================================================
* *
@ -30,8 +30,6 @@
#include <convar.h> #include <convar.h>
#include <eiface.h> #include <eiface.h>
#include <tier0/icommandline.h> #include <tier0/icommandline.h>
#include <sh_vector.h>
#include <sh_string.h>
#include "../metamod_util.h" #include "../metamod_util.h"
#include "provider_base.h" #include "provider_base.h"
#include "console.h" #include "console.h"
@ -39,31 +37,7 @@
#include <filesystem.h> #include <filesystem.h>
#include "metamod.h" #include "metamod.h"
#include <tier1/KeyValues.h> #include <tier1/KeyValues.h>
#if SOURCE_ENGINE == SE_DOTA
#include <iserver.h>
#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 */ /* Imports */
#if SOURCE_ENGINE < SE_ORANGEBOX #if SOURCE_ENGINE < SE_ORANGEBOX
@ -71,239 +45,21 @@ struct UsrMsgInfo
DLL_IMPORT ICommandLine *CommandLine(); DLL_IMPORT ICommandLine *CommandLine();
#endif #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(); void _ServerCommand();
/* Variables */ /* Variables */
static BaseProvider g_Ep1Provider;
static List<ConCommandBase *> conbases_unreg; static List<ConCommandBase *> conbases_unreg;
static CVector<UsrMsgInfo> usermsgs_list;
static jmp_buf usermsg_end;
static bool g_bOriginalEngine = false;
ICvar *icvar = NULL; ICvar *icvar = NULL;
IFileSystem *baseFs = NULL;
IServerGameDLL *server = NULL; IServerGameDLL *server = NULL;
#if SOURCE_ENGINE == SE_DOTA
static ISource2ServerConfig *serverconfig = NULL;
INetworkServerService *netservice = NULL;
IEngineServiceMgr *enginesvcmgr = NULL;
#endif
IVEngineServer *engine = NULL; IVEngineServer *engine = NULL;
IServerGameClients *gameclients = NULL; IServerGameClients *gameclients = NULL;
CGlobalVars *gpGlobals = 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() bool BaseProvider::IsRemotePrintingAvailable()
{ {
return true; 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) bool BaseProvider::IsSourceEngineBuildCompatible(int build)
{ {
return (build == SOURCE_ENGINE_ORIGINAL return (build == SOURCE_ENGINE_ORIGINAL
@ -352,54 +108,6 @@ bool BaseProvider::LogMessage(const char *buffer)
return true; 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, ...) void BaseProvider::DisplayError(const char *fmt, ...)
{ {
va_list ap; va_list ap;
@ -424,444 +132,14 @@ void BaseProvider::DisplayWarning(const char *fmt, ...)
Warning("%s", buffer); Warning("%s", buffer);
} }
IConCommandBaseAccessor *BaseProvider::GetConCommandBaseAccessor() void BaseProvider::DisplayDevMsg(const char* fmt, ...)
{ {
return &g_SMConVarAccessor; va_list ap;
} char buffer[2048];
bool BaseProvider::RegisterConCommandBase(ConCommandBase *pCommand) va_start(ap, fmt);
{ UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap);
return g_SMConVarAccessor.Register(pCommand); va_end(ap);
}
DevMsg("%s", buffer);
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;
} }

View File

@ -50,41 +50,47 @@ class INetworkGameServer;
class BaseProvider : public IMetamodSourceProvider 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 IsSourceEngineBuildCompatible(int build) override;
virtual bool GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo) override;
virtual bool LogMessage(const char *buffer) override; virtual bool LogMessage(const char *buffer) override;
virtual const char *GetCommandLineValue(const char *key, const char *defval) override; virtual const char *GetCommandLineValue(const char *key, const char *defval) override;
virtual void ConsolePrint(const char *msg) override;
virtual bool IsRemotePrintingAvailable() override; virtual bool IsRemotePrintingAvailable() override;
virtual void ClientConsolePrint(edict_t *client, const char *msg) override;
virtual void DisplayError(const char *fmt, ...) override; virtual void DisplayError(const char *fmt, ...) override;
virtual void DisplayWarning(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 int TryServerGameDLL(const char *iface) override;
virtual void Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, CreateInterfaceFn serverFactory) override; public:
void Notify_DLLShutdown_Pre() override; void SetCallbacks(IMetamodSourceProviderCallbacks* pCallbacks) override final
virtual void ServerCommand(const char *cmd) override; {
virtual ConVar *CreateConVar(const char *name, m_pCallbacks = pCallbacks;
const char *defval, }
const char *help, protected:
int flags) override; IMetamodSourceProviderCallbacks* m_pCallbacks = nullptr;
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
}; };
extern IVEngineServer *engine; extern IVEngineServer *engine;

View File

@ -24,6 +24,625 @@
*/ */
#include "provider_source.h" #include "provider_source.h"
#include "../console.h"
#include <metamod.h>
#include <metamod_util.h>
#include <metamod_console.h>
#include <KeyValues.h>
#include <filesystem.h>
#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; static SourceProvider g_SourceProvider;

View File

@ -29,10 +29,73 @@
#define _INCLUDE_METAMOD_SOURCE_SOURCE1_PROVIDER_H_ #define _INCLUDE_METAMOD_SOURCE_SOURCE1_PROVIDER_H_
#include "../provider_base.h" #include "../provider_base.h"
#include <string>
#include <vector>
class SourceProvider : public BaseProvider 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<UsrMsgInfo> usermsgs_list;
bool bOriginalEngine = false;
}; };
#endif #endif

View File

@ -24,6 +24,401 @@
*/ */
#include "provider_source2.h" #include "provider_source2.h"
#include "../console.h"
#include <metamod.h>
#include <metamod_util.h>
#include <metamod_console.h>
#include <amtl/am-string.h>
#include <eiface.h>
#include <KeyValues.h>
#include <filesystem.h>
#include <iserver.h>
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<INetworkGameClient *> *, 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<INetworkGameClient *> *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; static Source2Provider g_Source2Provider;

View File

@ -29,10 +29,54 @@
#define _INCLUDE_METAMOD_SOURCE_SOURCE2_PROVIDER_H_ #define _INCLUDE_METAMOD_SOURCE_SOURCE2_PROVIDER_H_
#include "../provider_base.h" #include "../provider_base.h"
#include <tier1/utlvector.h>
#include <string>
// 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 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<INetworkGameClient *> *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 #endif