Cassandra/cassandra.cpp
2019-09-25 11:36:02 +02:00

255 lines
6.2 KiB
C++

/**
* vim: set ts=4 sw=4 tw=99 noet :
* ======================================================
* Metamod:Source Stub Plugin
* Written by AlliedModders LLC.
* ======================================================
*
* 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.
*
* This stub plugin is public domain.
*/
#include "cassandra.h"
using namespace SourceHook;
Cassandra g_Cassandra;
IVEngineServer *engine = NULL;
IServerGameDLL *gamedll = NULL;
ICvar *icvar = NULL;
IPlayerInfoManager *playerinfo = NULL;
CGlobalVars *gpGlobals = NULL;
PLUGIN_EXPOSE(Cassandra, g_Cassandra);
SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool);
SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool);
static const int installedsignals[] = { SIGALRM, SIGABRT, SIGSEGV, SIGBUS };
static const size_t installedlen = (sizeof(installedsignals) / sizeof(int));
static struct sigaction savedsig[installedlen];
static int signalmap[63]; /* Fuck you, SIGRTMAX. */
ConVar *g_pPacketThread = NULL;
bool Cassandra::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
{
signalmap[SIGALRM] = 0;
signalmap[SIGABRT] = 1;
signalmap[SIGSEGV] = 2;
signalmap[SIGBUS] = 3;
PLUGIN_SAVEVARS();
GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER);
GET_V_IFACE_ANY(GetServerFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL);
GET_V_IFACE_CURRENT(GetEngineFactory, icvar, ICvar, CVAR_INTERFACE_VERSION);
GET_V_IFACE_CURRENT(GetServerFactory, playerinfo, IPlayerInfoManager, INTERFACEVERSION_PLAYERINFOMANAGER);
this->m_iGameFrameHook = SH_ADD_VPHOOK(IServerGameDLL, GameFrame, gamedll, SH_MEMBER(this, &Cassandra::OnGameFrame), false);
this->m_iLevelInitHook = SH_ADD_HOOK(IServerGameDLL, LevelInit, gamedll, SH_MEMBER(this, &Cassandra::LevelInit), false);
gpGlobals = ismm->GetCGlobals();
g_pPacketThread = icvar->FindVar("net_queued_packet_thread");
return true;
}
bool Cassandra::Unload(char *error, size_t maxlen)
{
SH_REMOVE_HOOK_ID(this->m_iGameFrameHook);
SH_REMOVE_HOOK_ID(this->m_iLevelInitHook);
this->RemoveSignalHandler();
alarm(0);
return true;
}
bool Cassandra::LevelInit(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background)
{
alarm(12); /* Level Change, alarm later in-case something happens. */
RETURN_META_VALUE(MRES_IGNORED, true);
}
void Cassandra::OnGameFrame(bool simulating)
{
alarm(7); /* Reset our Alarm here. We're still ticking. */
this->InstallSignalHandler();
RETURN_META(MRES_IGNORED);
}
void Cassandra::InstallSignalHandler(void)
{
static const int ignoredsignals[] = { SIGINT, SIGQUIT, SIGALRM, SIGABRT };
struct sigaction sig;
unsigned iter;
for (iter = 0; iter < installedlen; ++iter)
{
sigaction(installedsignals[iter], NULL, &sig);
if (sig.sa_sigaction != &(SignalAction))
{
break;
}
}
if (iter == installedlen)
{
return;
}
sigset_t sigset;
sigemptyset(&sigset);
for (iter = 0; iter < installedlen; ++iter)
{
sigaddset(&sigset, ignoredsignals[iter]);
}
sig.sa_mask = sigset;
sig.sa_handler = NULL;
sig.sa_sigaction = &(SignalAction); /* We don't really care who gets installed. This is a union on some platforms so we prefer this. */
sig.sa_flags = SA_ONSTACK|SA_SIGINFO;
for (iter = 0; iter < installedlen; ++iter)
{
sigaction(installedsignals[iter], &sig, &savedsig[iter]);
}
}
void Cassandra::RemoveSignalHandler(void)
{
for (unsigned iter = 0; iter < installedlen; ++iter)
{
sigaction(installedsignals[iter], &savedsig[iter], NULL);
}
}
void Cassandra::Hooped(void)
{
if (g_pPacketThread != NULL)
{
g_pPacketThread->SetValue(0);
}
VCRHook_recvfrom = &RecvFrom;
for (int counter = 1; counter <= gpGlobals->maxClients; ++counter)
{
edict_t *pEdict = PEntityOfEntIndex(counter);
if (pEdict == NULL || pEdict->IsFree())
{
continue;
}
IPlayerInfo *pInfo = playerinfo->GetPlayerInfo(pEdict);
INetChannel *netchan = static_cast<INetChannel *>(engine->GetPlayerNetInfo(counter));
if (pInfo == NULL || !(pInfo->IsConnected()) || pInfo->IsFakeClient() || netchan == NULL)
{
continue;
}
engine->ClientPrintf(pEdict, "\x05[Cassandra]\x04 Server Crashed. Recovering...\n");
for (unsigned char count = 4; count != 0; --count)
{
engine->ClientCommand(pEdict, "retry");
netchan->Transmit();
}
}
}
void Cassandra::TakeStackTrace(const char *pInput)
{
/*
backward::Printer p;
p.snippet = true;
p.object = true;
p.address = true;
Stacktrace st;
st.load_here(32);
FILE *pFile = fopen(pInput, "abw");
if (pFile != NULL)
{
p.color = false;
p.print(&st, pFile);
fclose(pFile);
}
else
{
p.color = true;
p.Print(&st);
}
*/
}
int RecvFrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
{
/* This should take all packets, then do nothing with them. We're dead. */
return -ECONNREFUSED;
}
void SignalAction(int sig, siginfo_t *pInfo, void *pData)
{
static bool used = false;
if (used == true)
{
signal(sig, SIG_DFL);
return;
}
used = true;
alarm(12);
g_Cassandra.Hooped();
g_Cassandra.TakeStackTrace("Cassandra.txt");
g_Cassandra.RemoveSignalHandler();
signal(sig, savedsig[signalmap[sig]].sa_handler);
/* Exit for good measure. */
exit(EXIT_FAILURE);
}
const char *Cassandra::GetLicense()
{
return "GPL";
}
const char *Cassandra::GetVersion()
{
return "1.0";
}
const char *Cassandra::GetDate()
{
return __DATE__;
}
const char *Cassandra::GetLogTag()
{
return "Cassandra";
}
const char *Cassandra::GetAuthor()
{
return "Kyle (KyleS) Sanderson";
}
const char *Cassandra::GetDescription()
{
return "Reconnect players on crash.";
}
const char *Cassandra::GetName()
{
return "Cassandra";
}
const char *Cassandra::GetURL()
{
return "http://www.AlliedMods.net";
}