255 lines
6.2 KiB
C++
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";
|
||
|
}
|