1
0
mirror of https://github.com/alliedmodders/metamod-source.git synced 2024-12-12 00:08:50 +01:00
HLMetaModOfficial/core/provider/source2/provider_source2.cpp

425 lines
12 KiB
C++

/**
* vim: set ts=4 sw=4 tw=99 noet :
* ======================================================
* Metamod:Source
* Copyright (C) 2004-2023 AlliedModders LLC and authors.
* All rights reserved.
* ======================================================
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "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;
IMetamodSourceProvider* provider = &g_Source2Provider;