/** * 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(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"; }