Refactor SourceTV instance hooking

Keep the instances in seperate wrappers to clear up the hooks. This
allows for some OnServerStart and OnServerShutdown forwards.

To prepare support for relay servers, CHLTVServer::Shutdown is hooked to
detect shutdown instead of relying on the director unregistering the
instance.
This commit is contained in:
Peace-Maker 2016-03-06 12:16:38 +01:00
parent fb03d5f2d5
commit fabdbe7d12
10 changed files with 585 additions and 281 deletions

View File

@ -30,15 +30,13 @@
*/
#include "extension.h"
#include "hltvserverwrapper.h"
#include "forwards.h"
#include "natives.h"
IHLTVDirector *hltvdirector = nullptr;
IHLTVServer *hltvserver = nullptr;
IDemoRecorder *demorecorder = nullptr;
void *host_client = nullptr;
void *old_host_client = nullptr;
bool g_HostClientOverridden = false;
HLTVServerWrapper *hltvserver = nullptr;
IGameEventManager2 *gameevents = nullptr;
CGlobalVars *gpGlobals = nullptr;
@ -58,19 +56,8 @@ SH_DECL_HOOK1_void(IHLTVDirector, AddHLTVServer, SH_NOATTRIB, 0, IHLTVServer *);
SH_DECL_HOOK1_void(IHLTVDirector, RemoveHLTVServer, SH_NOATTRIB, 0, IHLTVServer *);
#else
SH_DECL_HOOK1_void(IHLTVDirector, SetHLTVServer, SH_NOATTRIB, 0, IHLTVServer *);
// Stuff to print to demo console
SH_DECL_HOOK0_void_vafmt(IClient, ClientPrintf, SH_NOATTRIB, 0);
// This should be large enough.
#define FAKE_VTBL_LENGTH 70
static void *FakeNetChanVtbl[FAKE_VTBL_LENGTH];
static void *FakeNetChan = &FakeNetChanVtbl;
SH_DECL_MANUALHOOK3(NetChan_SendNetMsg, 0, 0, 0, bool, INetMessage &, bool, bool);
#endif
SH_DECL_HOOK1(IClient, ExecuteStringCommand, SH_NOATTRIB, 0, bool, const char *);
/**
* @file extension.cpp
* @brief Implement extension code here.
@ -106,33 +93,7 @@ bool SourceTVManager::SDK_OnLoad(char *error, size_t maxlength, bool late)
smutils->LogError(myself, "Failed to find host_client pointer. Server might crash when executing commands on SourceTV bot.");
}
#if SOURCE_ENGINE != SE_CSGO
int offset;
if (g_pGameConf->GetOffset("CNetChan::SendNetMsg", &offset))
{
if (offset >= FAKE_VTBL_LENGTH)
{
smutils->LogError(myself, "CNetChan::SendNetMsg offset too big. Need to raise define and recompile. Contact the author.");
}
else
{
// This is a hack. Bots don't have a net channel, but ClientPrintf tries to call m_NetChannel->SendNetMsg directly.
// CGameClient::SendNetMsg would have redirected it to the hltvserver correctly, but isn't used there..
// We craft a fake object with a large enough "vtable" and hook it using sourcehook.
// Before a call to ClientPrintf, this fake object is set as CBaseClient::m_NetChannel, so ClientPrintf creates
// the SVC_Print INetMessage and calls our "hooked" m_NetChannel->SendNetMsg function.
// In that function we just call CGameClient::SendNetMsg with the given INetMessage to flow it through the same
// path as other net messages.
SH_MANUALHOOK_RECONFIGURE(NetChan_SendNetMsg, offset, 0, 0);
SH_ADD_MANUALHOOK(NetChan_SendNetMsg, &FakeNetChan, SH_MEMBER(this, &SourceTVManager::OnHLTVBotNetChanSendNetMsg), false);
g_SendNetMsgHooked = true;
}
}
else
{
smutils->LogError(myself, "Failed to find CNetChan::SendNetMsg offset. Can't print to demo console.");
}
#endif
g_HLTVServers.InitHooks();
sharesys->AddNatives(myself, sourcetv_natives);
sharesys->RegisterLibrary(myself, "sourcetvmanager");
@ -160,19 +121,19 @@ void SourceTVManager::SDK_OnAllLoaded()
smutils->LogError(myself, "Failed to get IServer interface from SDKTools. Some functions won't work.");
#if SOURCE_ENGINE == SE_CSGO
if (hltvdirector->GetHLTVServerCount() > 0)
SelectSourceTVServer(hltvdirector->GetHLTVServer(0));
// Hook all the exisiting servers.
for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++)
{
HookSourceTVServer(hltvdirector->GetHLTVServer(i));
g_HLTVServers.AddServer(hltvdirector->GetHLTVServer(i));
}
if (hltvdirector->GetHLTVServerCount() > 0)
SelectSourceTVServer(hltvdirector->GetHLTVServer(0));
#else
if (hltvdirector->GetHLTVServer())
{
g_HLTVServers.AddServer(hltvdirector->GetHLTVServer());
SelectSourceTVServer(hltvdirector->GetHLTVServer());
HookSourceTVServer(hltvdirector->GetHLTVServer());
}
#endif
}
@ -204,27 +165,23 @@ void SourceTVManager::SDK_OnUnload()
SH_REMOVE_HOOK(IHLTVDirector, RemoveHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnRemoveHLTVServer_Post), true);
#else
SH_REMOVE_HOOK(IHLTVDirector, SetHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnSetHLTVServer_Post), true);
if (g_SendNetMsgHooked)
{
SH_REMOVE_MANUALHOOK(NetChan_SendNetMsg, &FakeNetChan, SH_MEMBER(this, &SourceTVManager::OnHLTVBotNetChanSendNetMsg), false);
g_SendNetMsgHooked = false;
}
#endif
g_HLTVServers.ShutdownHooks();
gameconfs->CloseGameConfigFile(g_pGameConf);
#if SOURCE_ENGINE == SE_CSGO
// Unhook all the existing servers.
for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++)
{
UnhookSourceTVServer(hltvdirector->GetHLTVServer(i));
// We don't know if the extension is just being unloaded or the server is shutting down.
// So don't inform the plugins of this removal.
g_HLTVServers.RemoveServer(hltvdirector->GetHLTVServer(i), false);
}
#else
// Unhook the server
if (hltvdirector->GetHLTVServer())
UnhookSourceTVServer(hltvdirector->GetHLTVServer());
g_HLTVServers.RemoveServer(hltvdirector->GetHLTVServer(), false);
#endif
g_pSTVForwards.Shutdown();
}
@ -237,113 +194,43 @@ bool SourceTVManager::QueryRunning(char *error, size_t maxlength)
return true;
}
void SourceTVManager::HookSourceTVServer(IHLTVServer *hltv)
{
if (hltv != nullptr)
{
g_pSTVForwards.HookServer(hltv->GetBaseServer());
g_pSTVForwards.HookRecorder(GetDemoRecorderPtr(hltv));
if (iserver)
{
IClient *pClient = iserver->GetClient(hltv->GetHLTVSlot());
if (pClient)
{
SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand), false);
SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand_Post), true);
#if SOURCE_ENGINE != SE_CSGO
SH_ADD_HOOK(IClient, ClientPrintf, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotClientPrintf_Post), false);
#endif
}
}
}
}
void SourceTVManager::UnhookSourceTVServer(IHLTVServer *hltv)
{
if (hltv != nullptr)
{
g_pSTVForwards.UnhookServer(hltv->GetBaseServer());
g_pSTVForwards.UnhookRecorder(GetDemoRecorderPtr(hltv));
if (iserver)
{
IClient *pClient = iserver->GetClient(hltv->GetHLTVSlot());
if (pClient)
{
SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand), false);
SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand_Post), true);
#if SOURCE_ENGINE != SE_CSGO
SH_REMOVE_HOOK(IClient, ClientPrintf, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotClientPrintf_Post), false);
#endif
}
}
}
}
void SourceTVManager::SelectSourceTVServer(IHLTVServer *hltv)
{
// Select the new server.
hltvserver = hltv;
demorecorder = GetDemoRecorderPtr(hltvserver);
}
IDemoRecorder *SourceTVManager::GetDemoRecorderPtr(IHLTVServer *hltv)
{
static int offset = -1;
if (offset == -1)
{
void *addr;
if (!g_pGameConf->GetAddress("CHLTVServer::m_DemoRecorder", &addr))
{
smutils->LogError(myself, "Failed to get CHLTVServer::m_DemoRecorder offset.");
return nullptr;
}
*(int **)&offset = (int *)addr;
}
if (hltv)
{
#if SOURCE_ENGINE == SE_CSGO
return (IDemoRecorder *)((intptr_t)hltv + offset);
#else
IServer *baseServer = hltv->GetBaseServer();
return (IDemoRecorder *)((intptr_t)baseServer + offset);
#endif
}
else
{
return nullptr;
}
hltvserver = g_HLTVServers.GetWrapper(hltv);
}
#if SOURCE_ENGINE == SE_CSGO
void SourceTVManager::OnAddHLTVServer_Post(IHLTVServer *hltv)
{
HookSourceTVServer(hltv);
g_HLTVServers.AddServer(hltv);
// We already selected some SourceTV server. Keep it.
if (hltvserver != nullptr)
RETURN_META(MRES_IGNORED);
// This is the first SourceTV server to be added. Hook it.
// This is the first SourceTV server to be added.
SelectSourceTVServer(hltv);
RETURN_META(MRES_IGNORED);
}
void SourceTVManager::OnRemoveHLTVServer_Post(IHLTVServer *hltv)
{
UnhookSourceTVServer(hltv);
HLTVServerWrapper *wrapper = g_HLTVServers.GetWrapper(hltv);
if (!wrapper)
RETURN_META(MRES_IGNORED);
// With the CHLTVServer::Shutdown hook, this isn't needed?
// Doesn't hurt either..
g_HLTVServers.RemoveServer(hltv, true);
// We got this SourceTV server selected. Now it's gone :(
if (hltvserver == hltv)
if (hltvserver == wrapper)
{
// Is there another one available? Try to keep us operable.
if (hltvdirector->GetHLTVServerCount() > 0)
{
SelectSourceTVServer(hltvdirector->GetHLTVServer(0));
HookSourceTVServer(hltvserver);
}
// No sourcetv active.
else
@ -354,45 +241,6 @@ void SourceTVManager::OnRemoveHLTVServer_Post(IHLTVServer *hltv)
RETURN_META(MRES_IGNORED);
}
#else
void SourceTVManager::OnHLTVBotClientPrintf_Post(const char* buf)
{
// Craft our own "NetChan" pointer
static int offset = -1;
if (!g_pGameConf->GetOffset("CBaseClient::m_NetChannel", &offset) || offset == -1)
{
smutils->LogError(myself, "Failed to find CBaseClient::m_NetChannel offset. Can't print to demo console.");
RETURN_META(MRES_IGNORED);
}
IClient *pClient = META_IFACEPTR(IClient);
void *pNetChannel = (void *)((char *)pClient + offset);
// Set our fake netchannel
*(void **)pNetChannel = &FakeNetChan;
// Call ClientPrintf again, this time with a "Netchannel" set on the bot.
// This will call our own OnHLTVBotNetChanSendNetMsg function
SH_CALL(pClient, &IClient::ClientPrintf)("%s", buf);
// Set the fake netchannel back to 0.
*(void **)pNetChannel = nullptr;
RETURN_META(MRES_IGNORED);
}
bool SourceTVManager::OnHLTVBotNetChanSendNetMsg(INetMessage &msg, bool bForceReliable, bool bVoice)
{
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot());
if (!pClient)
RETURN_META_VALUE(MRES_SUPERCEDE, false);
// Let the message flow through the intended path like CGameClient::SendNetMsg wants to.
bool bRetSent = pClient->SendNetMsg(msg, bForceReliable);
// It's important to supercede, because there is no original function to call.
// (the "vtable" was empty before hooking it)
// See FakeNetChan variable at the top.
RETURN_META_VALUE(MRES_SUPERCEDE, bRetSent);
}
void SourceTVManager::OnSetHLTVServer_Post(IHLTVServer *hltv)
{
// Server shut down?
@ -402,53 +250,15 @@ void SourceTVManager::OnSetHLTVServer_Post(IHLTVServer *hltv)
if (!hltvserver)
RETURN_META(MRES_IGNORED);
UnhookSourceTVServer(hltvserver);
// With the CHLTVServer::Shutdown hook, this isn't needed?
// Doesn't hurt either..
g_HLTVServers.RemoveServer(hltvserver->GetHLTVServer(), true);
}
else
{
HookSourceTVServer(hltv);
g_HLTVServers.AddServer(hltv);
}
SelectSourceTVServer(hltv);
RETURN_META(MRES_IGNORED);
}
#endif
// When bots issue a command that would print stuff to their console,
// the server might crash, because ExecuteStringCommand doesn't set the
// global host_client pointer to the client on whom the command is run.
// Host_Client_Printf blatantly tries to call host_client->ClientPrintf
// while the pointer might point to some other player or garbage.
// This leads to e.g. the output of the "status" command not being
// recorded in the SourceTV demo.
// The approach here is to set host_client correctly for the SourceTV
// bot and reset it to the old value after command execution.
bool SourceTVManager::OnHLTVBotExecuteStringCommand(const char *s)
{
if (!hltvserver || !iserver || !host_client)
RETURN_META_VALUE(MRES_IGNORED, 0);
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot());
if (!pClient)
RETURN_META_VALUE(MRES_IGNORED, 0);
// The IClient vtable is +4 from the CBaseClient vtable due to multiple inheritance.
void *pGameClient = (void *)((intptr_t)pClient - 4);
old_host_client = *(void **)host_client;
*(void **)host_client = pGameClient;
g_HostClientOverridden = true;
RETURN_META_VALUE(MRES_IGNORED, 0);
}
bool SourceTVManager::OnHLTVBotExecuteStringCommand_Post(const char *s)
{
if (!host_client || !g_HostClientOverridden)
RETURN_META_VALUE(MRES_IGNORED, 0);
*(void **)host_client = old_host_client;
g_HostClientOverridden = false;
RETURN_META_VALUE(MRES_IGNORED, 0);
}

View File

@ -52,6 +52,7 @@
#include "hltvclientwrapper.h"
class INetMessage;
class HLTVServerWrapper;
extern ConVar tv_force_steamauth;
@ -135,7 +136,6 @@ public: // IConCommandBaseAccessor
public:
void SelectSourceTVServer(IHLTVServer *hltv);
IDemoRecorder *GetDemoRecorderPtr(IHLTVServer *hltvserver);
private:
#if SOURCE_ENGINE == SE_CSGO
@ -143,16 +143,7 @@ private:
void OnRemoveHLTVServer_Post(IHLTVServer *hltv);
#else
void OnSetHLTVServer_Post(IHLTVServer *hltv);
bool OnHLTVBotNetChanSendNetMsg(INetMessage &msg, bool bForceReliable, bool bVoice);
void OnHLTVBotClientPrintf_Post(const char *buf);
#endif
bool OnHLTVBotExecuteStringCommand(const char *s);
bool OnHLTVBotExecuteStringCommand_Post(const char *s);
private:
void HookSourceTVServer(IHLTVServer *hltv);
void UnhookSourceTVServer(IHLTVServer *hltv);
};
/* Interfaces from SourceMod */
@ -167,7 +158,7 @@ extern IGameEventManager2 *gameevents;
extern ICvar *icvar;
extern IHLTVDirector *hltvdirector;
extern IHLTVServer *hltvserver;
extern IDemoRecorder *demorecorder;
extern HLTVServerWrapper *hltvserver;
extern void *host_client;
#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_

View File

@ -104,6 +104,9 @@ void CForwardManager::Init()
m_SpectatorDisconnectFwd = forwards->CreateForward("SourceTV_OnSpectatorDisconnect", ET_Ignore, 2, NULL, Param_Cell, Param_String);
m_SpectatorDisconnectedFwd = forwards->CreateForward("SourceTV_OnSpectatorDisconnected", ET_Ignore, 2, NULL, Param_Cell, Param_String);
m_SpectatorPutInServerFwd = forwards->CreateForward("SourceTV_OnSpectatorPutInServer", ET_Ignore, 1, NULL, Param_Cell);
m_ServerStartFwd = forwards->CreateForward("SourceTV_OnServerStart", ET_Ignore, 1, NULL, Param_Cell);
m_ServerShutdownFwd = forwards->CreateForward("SourceTV_OnServerShutdown", ET_Ignore, 1, NULL, Param_Cell);
}
void CForwardManager::Shutdown()
@ -115,6 +118,9 @@ void CForwardManager::Shutdown()
forwards->ReleaseForward(m_SpectatorDisconnectFwd);
forwards->ReleaseForward(m_SpectatorDisconnectedFwd);
forwards->ReleaseForward(m_SpectatorPutInServerFwd);
forwards->ReleaseForward(m_ServerStartFwd);
forwards->ReleaseForward(m_ServerShutdownFwd);
}
void CForwardManager::HookRecorder(IDemoRecorder *recorder)
@ -188,6 +194,18 @@ void CForwardManager::UnhookClient(IClient *client)
SH_REMOVE_HOOK(IClient, Disconnect, client, SH_MEMBER(this, &CForwardManager::OnSpectatorDisconnect), false);
}
void CForwardManager::CallOnServerStart(IHLTVServer *server)
{
m_ServerStartFwd->PushCell(0); // TODO: Get right hltvinstance
m_ServerStartFwd->Execute();
}
void CForwardManager::CallOnServerShutdown(IHLTVServer *server)
{
m_ServerShutdownFwd->PushCell(0); // TODO: Get right hltvinstance
m_ServerShutdownFwd->Execute();
}
#if SOURCE_ENGINE == SE_CSGO
static bool ExtractPlayerName(CUtlVector<NetMsg_SplitPlayerConnect *> &pSplitPlayerConnectVector, char *name, int maxlen)
{

View File

@ -69,6 +69,9 @@ public:
void HookServer(IServer *server);
void UnhookServer(IServer *server);
void CallOnServerStart(IHLTVServer *server);
void CallOnServerShutdown(IHLTVServer *server);
private:
void HookClient(IClient *client);
void UnhookClient(IClient *client);
@ -96,6 +99,9 @@ private:
IForward *m_SpectatorDisconnectedFwd;
IForward *m_SpectatorPutInServerFwd;
IForward *m_ServerStartFwd;
IForward *m_ServerShutdownFwd;
bool m_bHasClientConnectOffset = false;
bool m_bHasRejectConnectionOffset = false;
bool m_bHasGetChallengeTypeOffset = false;

350
hltvserverwrapper.cpp Normal file
View File

@ -0,0 +1,350 @@
#include "hltvserverwrapper.h"
#include "forwards.h"
void *old_host_client = nullptr;
bool g_HostClientOverridden = false;
SH_DECL_HOOK1(IClient, ExecuteStringCommand, SH_NOATTRIB, 0, bool, const char *);
SH_DECL_MANUALHOOK0_void(CHLTVServer_Shutdown, 0, 0, 0);
#if SOURCE_ENGINE != SE_CSGO
// Stuff to print to demo console
SH_DECL_HOOK0_void_vafmt(IClient, ClientPrintf, SH_NOATTRIB, 0);
// This should be large enough.
#define FAKE_VTBL_LENGTH 70
static void *FakeNetChanVtbl[FAKE_VTBL_LENGTH];
static void *FakeNetChan = &FakeNetChanVtbl;
SH_DECL_MANUALHOOK3(NetChan_SendNetMsg, 0, 0, 0, bool, INetMessage &, bool, bool);
#endif // SOURCE_ENGINE != SE_CSGO
HLTVServerWrapper::HLTVServerWrapper(IHLTVServer *hltvserver)
{
m_HLTVServer = hltvserver;
m_DemoRecorder = g_HLTVServers.GetDemoRecorderPtr(hltvserver);
m_Connected = true;
Hook();
// Inform the plugins
g_pSTVForwards.CallOnServerStart(hltvserver);
}
void HLTVServerWrapper::Shutdown(bool bInformPlugins)
{
if (!m_Connected)
return;
if (bInformPlugins)
g_pSTVForwards.CallOnServerShutdown(m_HLTVServer);
Unhook();
m_HLTVServer = nullptr;
m_DemoRecorder = nullptr;
m_Connected = false;
}
IServer *HLTVServerWrapper::GetBaseServer()
{
return m_HLTVServer->GetBaseServer();
}
IHLTVServer *HLTVServerWrapper::GetHLTVServer()
{
return m_HLTVServer;
}
IDemoRecorder *HLTVServerWrapper::GetDemoRecorder()
{
return m_DemoRecorder;
}
int HLTVServerWrapper::GetInstanceNumber()
{
return g_HLTVServers.GetInstanceNumber(m_HLTVServer);
}
void HLTVServerWrapper::Hook()
{
if (!m_Connected)
return;
g_pSTVForwards.HookServer(m_HLTVServer->GetBaseServer());
g_pSTVForwards.HookRecorder(m_DemoRecorder);
if (g_HLTVServers.HasShutdownOffset())
SH_ADD_MANUALHOOK(CHLTVServer_Shutdown, m_HLTVServer->GetBaseServer(), SH_MEMBER(this, &HLTVServerWrapper::OnHLTVServerShutdown), false);
if (iserver)
{
IClient *pClient = iserver->GetClient(m_HLTVServer->GetHLTVSlot());
if (pClient)
{
SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotExecuteStringCommand), false);
SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotExecuteStringCommand_Post), true);
#if SOURCE_ENGINE != SE_CSGO
SH_ADD_HOOK(IClient, ClientPrintf, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotClientPrintf_Post), false);
#endif
}
}
}
void HLTVServerWrapper::Unhook()
{
if (!m_Connected)
return;
g_pSTVForwards.UnhookServer(m_HLTVServer->GetBaseServer());
g_pSTVForwards.UnhookRecorder(m_DemoRecorder);
if (g_HLTVServers.HasShutdownOffset())
SH_REMOVE_MANUALHOOK(CHLTVServer_Shutdown, m_HLTVServer->GetBaseServer(), SH_MEMBER(this, &HLTVServerWrapper::OnHLTVServerShutdown), false);
if (iserver)
{
IClient *pClient = iserver->GetClient(m_HLTVServer->GetHLTVSlot());
if (pClient)
{
SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotExecuteStringCommand), false);
SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotExecuteStringCommand_Post), true);
#if SOURCE_ENGINE != SE_CSGO
SH_REMOVE_HOOK(IClient, ClientPrintf, pClient, SH_MEMBER(this, &HLTVServerWrapper::OnHLTVBotClientPrintf_Post), false);
#endif
}
}
}
// CHLTVServer::Shutdown deregisters the hltvserver from the hltvdirector,
// so RemoveHLTVServer/SetHLTVServer(NULL) is called too on the master proxy.
void HLTVServerWrapper::OnHLTVServerShutdown()
{
if (!m_Connected)
RETURN_META(MRES_IGNORED);
Shutdown(true);
RETURN_META(MRES_IGNORED);
}
// When bots issue a command that would print stuff to their console,
// the server might crash, because ExecuteStringCommand doesn't set the
// global host_client pointer to the client on whom the command is run.
// Host_Client_Printf blatantly tries to call host_client->ClientPrintf
// while the pointer might point to some other player or garbage.
// This leads to e.g. the output of the "status" command not being
// recorded in the SourceTV demo.
// The approach here is to set host_client correctly for the SourceTV
// bot and reset it to the old value after command execution.
bool HLTVServerWrapper::OnHLTVBotExecuteStringCommand(const char *s)
{
if (!host_client)
RETURN_META_VALUE(MRES_IGNORED, 0);
IClient *pClient = META_IFACEPTR(IClient);
if (!pClient)
RETURN_META_VALUE(MRES_IGNORED, 0);
// The IClient vtable is +4 from the CBaseClient vtable due to multiple inheritance.
void *pGameClient = (void *)((intptr_t)pClient - 4);
old_host_client = *(void **)host_client;
*(void **)host_client = pGameClient;
g_HostClientOverridden = true;
RETURN_META_VALUE(MRES_IGNORED, 0);
}
bool HLTVServerWrapper::OnHLTVBotExecuteStringCommand_Post(const char *s)
{
if (!host_client || !g_HostClientOverridden)
RETURN_META_VALUE(MRES_IGNORED, 0);
*(void **)host_client = old_host_client;
g_HostClientOverridden = false;
RETURN_META_VALUE(MRES_IGNORED, 0);
}
#if SOURCE_ENGINE != SE_CSGO
void HLTVServerWrapper::OnHLTVBotClientPrintf_Post(const char* buf)
{
// Craft our own "NetChan" pointer
static int offset = -1;
if (!g_pGameConf->GetOffset("CBaseClient::m_NetChannel", &offset) || offset == -1)
{
smutils->LogError(myself, "Failed to find CBaseClient::m_NetChannel offset. Can't print to demo console.");
RETURN_META(MRES_IGNORED);
}
IClient *pClient = META_IFACEPTR(IClient);
void *pNetChannel = (void *)((char *)pClient + offset);
// Set our fake netchannel
*(void **)pNetChannel = &FakeNetChan;
// Call ClientPrintf again, this time with a "Netchannel" set on the bot.
// This will call our own OnHLTVBotNetChanSendNetMsg function
SH_CALL(pClient, &IClient::ClientPrintf)("%s", buf);
// Set the fake netchannel back to 0.
*(void **)pNetChannel = nullptr;
RETURN_META(MRES_IGNORED);
}
#endif
/**
* Manage the wrappers!
*/
void HLTVServerWrapperManager::InitHooks()
{
int offset;
if (g_pGameConf->GetOffset("CHLTVServer::Shutdown", &offset))
{
SH_MANUALHOOK_RECONFIGURE(CHLTVServer_Shutdown, offset, 0, 0);
m_bHasShutdownOffset = true;
}
else
{
smutils->LogError(myself, "Failed to find CHLTVServer::Shutdown offset.");
}
#if SOURCE_ENGINE != SE_CSGO
if (g_pGameConf->GetOffset("CNetChan::SendNetMsg", &offset))
{
if (offset >= FAKE_VTBL_LENGTH)
{
smutils->LogError(myself, "CNetChan::SendNetMsg offset too big. Need to raise define and recompile. Contact the author.");
}
else
{
// This is a hack. Bots don't have a net channel, but ClientPrintf tries to call m_NetChannel->SendNetMsg directly.
// CGameClient::SendNetMsg would have redirected it to the hltvserver correctly, but isn't used there..
// We craft a fake object with a large enough "vtable" and hook it using sourcehook.
// Before a call to ClientPrintf, this fake object is set as CBaseClient::m_NetChannel, so ClientPrintf creates
// the SVC_Print INetMessage and calls our "hooked" m_NetChannel->SendNetMsg function.
// In that function we just call CGameClient::SendNetMsg with the given INetMessage to flow it through the same
// path as other net messages.
SH_MANUALHOOK_RECONFIGURE(NetChan_SendNetMsg, offset, 0, 0);
SH_ADD_MANUALHOOK(NetChan_SendNetMsg, &FakeNetChan, SH_MEMBER(this, &HLTVServerWrapperManager::OnHLTVBotNetChanSendNetMsg), false);
m_bSendNetMsgHooked = true;
}
}
else
{
smutils->LogError(myself, "Failed to find CNetChan::SendNetMsg offset. Can't print to demo console.");
}
#endif
}
void HLTVServerWrapperManager::ShutdownHooks()
{
#if SOURCE_ENGINE != SE_CSGO
if (m_bSendNetMsgHooked)
{
SH_REMOVE_MANUALHOOK(NetChan_SendNetMsg, &FakeNetChan, SH_MEMBER(this, &HLTVServerWrapperManager::OnHLTVBotNetChanSendNetMsg), false);
m_bSendNetMsgHooked = false;
}
#endif
}
void HLTVServerWrapperManager::AddServer(IHLTVServer *hltvserver)
{
HLTVServerWrapper *wrapper = new HLTVServerWrapper(hltvserver);
m_HLTVServers.append(wrapper);
}
void HLTVServerWrapperManager::RemoveServer(IHLTVServer *hltvserver, bool bInformPlugins)
{
for (unsigned int i = 0; i < m_HLTVServers.length(); i++)
{
HLTVServerWrapper *wrapper = m_HLTVServers[i];
if (wrapper->GetHLTVServer() != hltvserver)
continue;
wrapper->Shutdown(bInformPlugins);
m_HLTVServers.remove(i);
break;
}
}
HLTVServerWrapper *HLTVServerWrapperManager::GetWrapper(IHLTVServer *hltvserver)
{
for (unsigned int i = 0; i < m_HLTVServers.length(); i++)
{
HLTVServerWrapper *wrapper = m_HLTVServers[i];
if (wrapper->GetHLTVServer() == hltvserver)
return wrapper;
}
return nullptr;
}
int HLTVServerWrapperManager::GetInstanceNumber(IHLTVServer *hltvserver)
{
#if SOURCE_ENGINE == SE_CSGO
for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++)
{
if (hltvserver == hltvdirector->GetHLTVServer(i))
return i;
}
// We should have found it in the above loop :S
smutils->LogError(myself, "Failed to find IHLTVServer instance in director.");
return -1;
#else
return 0;
#endif
}
IDemoRecorder *HLTVServerWrapperManager::GetDemoRecorderPtr(IHLTVServer *hltv)
{
static int offset = -1;
if (offset == -1)
{
void *addr;
if (!g_pGameConf->GetAddress("CHLTVServer::m_DemoRecorder", &addr))
{
smutils->LogError(myself, "Failed to get CHLTVServer::m_DemoRecorder offset.");
return nullptr;
}
*(int **)&offset = (int *)addr;
}
if (hltv)
{
#if SOURCE_ENGINE == SE_CSGO
return (IDemoRecorder *)((intptr_t)hltv + offset);
#else
IServer *baseServer = hltv->GetBaseServer();
return (IDemoRecorder *)((intptr_t)baseServer + offset);
#endif
}
else
{
return nullptr;
}
}
bool HLTVServerWrapperManager::HasShutdownOffset()
{
return m_bHasShutdownOffset;
}
#if SOURCE_ENGINE != SE_CSGO
bool HLTVServerWrapperManager::OnHLTVBotNetChanSendNetMsg(INetMessage &msg, bool bForceReliable, bool bVoice)
{
// No need to worry about the right selected hltvserver, because there can only be one.
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVServer()->GetHLTVSlot());
if (!pClient)
RETURN_META_VALUE(MRES_SUPERCEDE, false);
// Let the message flow through the intended path like CGameClient::SendNetMsg wants to.
bool bRetSent = pClient->SendNetMsg(msg, bForceReliable);
// It's important to supercede, because there is no original function to call.
// (the "vtable" was empty before hooking it)
// See FakeNetChan variable at the top.
RETURN_META_VALUE(MRES_SUPERCEDE, bRetSent);
}
#endif
HLTVServerWrapperManager g_HLTVServers;

95
hltvserverwrapper.h Normal file
View File

@ -0,0 +1,95 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod SourceTV Manager Extension
* Copyright (C) 2004-2016 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_EXTENSION_HLTVSERVER_H_
#define _INCLUDE_SOURCEMOD_EXTENSION_HLTVSERVER_H_
#include "extension.h"
#include "amtl/am-vector.h"
#include "amtl/am-utility.h"
class HLTVServerWrapper {
public:
HLTVServerWrapper(IHLTVServer *hltvserver);
void Shutdown(bool bInformPlugins);
IHLTVServer *GetHLTVServer();
IServer *GetBaseServer();
IDemoRecorder *GetDemoRecorder();
int GetInstanceNumber();
private:
void Hook();
void Unhook();
// Hooks
bool OnHLTVBotExecuteStringCommand(const char *s);
bool OnHLTVBotExecuteStringCommand_Post(const char *s);
void OnHLTVServerShutdown();
#if SOURCE_ENGINE != SE_CSGO
void OnHLTVBotClientPrintf_Post(const char *buf);
#endif
private:
bool m_Connected = false;
IHLTVServer *m_HLTVServer = nullptr;
IDemoRecorder *m_DemoRecorder = nullptr;
};
class HLTVServerWrapperManager
{
public:
void InitHooks();
void ShutdownHooks();
void AddServer(IHLTVServer *hltvserver);
void RemoveServer(IHLTVServer *hltvserver, bool bInformPlugins);
HLTVServerWrapper *GetWrapper(IHLTVServer *hltvserver);
int GetInstanceNumber(IHLTVServer *hltvserver);
IDemoRecorder *GetDemoRecorderPtr(IHLTVServer *hltv);
bool HasShutdownOffset();
#if SOURCE_ENGINE != SE_CSGO
bool OnHLTVBotNetChanSendNetMsg(INetMessage &msg, bool bForceReliable, bool bVoice);
#endif
private:
#if SOURCE_ENGINE != SE_CSGO
bool m_bSendNetMsgHooked = false;
#endif
bool m_bHasShutdownOffset = false;
ke::Vector<ke::AutoPtr<HLTVServerWrapper>> m_HLTVServers;
};
extern HLTVServerWrapperManager g_HLTVServers;
#endif // _INCLUDE_SOURCEMOD_EXTENSION_HLTVSERVER_H_

View File

@ -31,6 +31,7 @@
#include "extension.h"
#include "natives.h"
#include "hltvserverwrapper.h"
#define TICK_INTERVAL (gpGlobals->interval_per_tick)
#define TIME_TO_TICKS( dt ) ( (int)( 0.5f + (float)(dt) / TICK_INTERVAL ) )
@ -94,20 +95,16 @@ static cell_t Native_GetSelectedServerInstance(IPluginContext *pContext, const c
if (hltvserver == nullptr)
return -1;
#if SOURCE_ENGINE == SE_CSGO
for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++)
{
if (hltvserver == hltvdirector->GetHLTVServer(i))
return i;
}
return hltvserver->GetInstanceNumber();
}
// We should have found it in the above loop :S
hltvserver = nullptr;
return -1;
#else
// There only is one hltv server.
return 0;
#endif
// native SourceTV_IsActive();
static cell_t Native_IsActive(IPluginContext *pContext, const cell_t *params)
{
if (hltvserver == nullptr)
return 0;
return hltvserver->GetBaseServer()->IsActive();
}
// native SourceTV_IsMasterProxy();
@ -116,7 +113,7 @@ static cell_t Native_IsMasterProxy(IPluginContext *pContext, const cell_t *param
if (hltvserver == nullptr)
return 0;
return hltvserver->IsMasterProxy();
return hltvserver->GetHLTVServer()->IsMasterProxy();
}
// native bool:SourceTV_GetServerIP(String:ip[], maxlen);
@ -125,7 +122,7 @@ static cell_t Native_GetServerIP(IPluginContext *pContext, const cell_t *params)
if (hltvserver == nullptr)
return 0;
const netadr_t *adr = hltvserver->GetRelayAddress();
const netadr_t *adr = hltvserver->GetHLTVServer()->GetRelayAddress();
char buf[16];
V_snprintf(buf, sizeof(buf), "%d.%d.%d.%d", adr->ip[0], adr->ip[1], adr->ip[2], adr->ip[3]);
@ -149,7 +146,7 @@ static cell_t Native_GetBotIndex(IPluginContext *pContext, const cell_t *params)
if (hltvserver == nullptr)
return 0;
return hltvserver->GetHLTVSlot() + 1;
return hltvserver->GetHLTVServer()->GetHLTVSlot() + 1;
}
// native bool:SourceTV_GetLocalStats(&proxies, &slots, &specs);
@ -159,7 +156,7 @@ static cell_t Native_GetLocalStats(IPluginContext *pContext, const cell_t *param
return 0;
int proxies, slots, specs;
hltvserver->GetLocalStats(proxies, slots, specs);
hltvserver->GetHLTVServer()->GetLocalStats(proxies, slots, specs);
cell_t *plProxies, *plSlots, *plSpecs;
pContext->LocalToPhysAddr(params[1], &plProxies);
@ -179,7 +176,7 @@ static cell_t Native_GetGlobalStats(IPluginContext *pContext, const cell_t *para
return 0;
int proxies, slots, specs;
hltvserver->GetGlobalStats(proxies, slots, specs);
hltvserver->GetHLTVServer()->GetGlobalStats(proxies, slots, specs);
cell_t *plProxies, *plSlots, *plSpecs;
pContext->LocalToPhysAddr(params[1], &plProxies);
@ -275,9 +272,9 @@ static cell_t Native_BroadcastScreenMessage(IPluginContext *pContext, const cell
int ret = 1;
bool bLocalOnly = params[1] != 0;
if (bLocalOnly)
hltvserver->BroadcastEvent(msg);
hltvserver->GetHLTVServer()->BroadcastEvent(msg);
else
ret = BroadcastEventLocal(hltvserver, msg, false);
ret = BroadcastEventLocal(hltvserver->GetHLTVServer(), msg, false);
gameevents->FreeEvent(msg);
@ -331,9 +328,9 @@ static cell_t Native_BroadcastChatMessage(IPluginContext *pContext, const cell_t
int ret = 1;
bool bLocalOnly = params[1] != 0;
if (bLocalOnly)
hltvserver->BroadcastEvent(msg);
hltvserver->GetHLTVServer()->BroadcastEvent(msg);
else
ret = BroadcastEventLocal(hltvserver, msg, false);
ret = BroadcastEventLocal(hltvserver->GetHLTVServer(), msg, false);
gameevents->FreeEvent(msg);
@ -395,7 +392,7 @@ static cell_t Native_ForceFixedCameraShot(IPluginContext *pContext, const cell_t
shot->SetInt("target", params[3] ? gamehelpers->ReferenceToIndex(params[3]) : 0);
shot->SetFloat("fov", sp_ctof(params[4]));
hltvserver->BroadcastEvent(shot);
hltvserver->GetHLTVServer()->BroadcastEvent(shot);
gameevents->FreeEvent(shot);
// Prevent auto director from changing shots until we allow it to again.
@ -437,7 +434,7 @@ static cell_t Native_ForceChaseCameraShot(IPluginContext *pContext, const cell_t
// Update director state
g_HLTVDirectorWrapper.SetPVSEntity(gamehelpers->ReferenceToIndex(params[1]));
hltvserver->BroadcastEvent(shot);
hltvserver->GetHLTVServer()->BroadcastEvent(shot);
gameevents->FreeEvent(shot);
// Prevent auto director from changing shots until we allow it to again.
@ -449,10 +446,7 @@ static cell_t Native_ForceChaseCameraShot(IPluginContext *pContext, const cell_t
// native bool:SourceTV_IsRecording();
static cell_t Native_IsRecording(IPluginContext *pContext, const cell_t *params)
{
if (demorecorder == nullptr)
return 0;
return demorecorder->IsRecording();
return hltvserver->GetDemoRecorder()->IsRecording();
}
// Checks in COM_IsValidPath in the engine
@ -464,7 +458,7 @@ static bool IsValidPath(const char *path)
// native bool:SourceTV_StartRecording(const String:sFilename[]);
static cell_t Native_StartRecording(IPluginContext *pContext, const cell_t *params)
{
if (hltvserver == nullptr || demorecorder == nullptr)
if (hltvserver == nullptr)
return 0;
// SourceTV is not active.
@ -472,11 +466,11 @@ static cell_t Native_StartRecording(IPluginContext *pContext, const cell_t *para
return 0;
// Only SourceTV Master can record demos instantly
if (!hltvserver->IsMasterProxy())
if (!hltvserver->GetHLTVServer()->IsMasterProxy())
return 0;
// already recording
if (demorecorder->IsRecording())
if (hltvserver->GetDemoRecorder()->IsRecording())
return 0;
char *pFile;
@ -512,7 +506,7 @@ static cell_t Native_StartRecording(IPluginContext *pContext, const cell_t *para
}
#endif
demorecorder->StartRecording(pPath, false);
hltvserver->GetDemoRecorder()->StartRecording(pPath, false);
return 1;
}
@ -520,17 +514,14 @@ static cell_t Native_StartRecording(IPluginContext *pContext, const cell_t *para
// native bool:SourceTV_StopRecording();
static cell_t Native_StopRecording(IPluginContext *pContext, const cell_t *params)
{
if (demorecorder == nullptr)
return 0;
if (!demorecorder->IsRecording())
if (!hltvserver->GetDemoRecorder()->IsRecording())
return 0;
#if SOURCE_ENGINE == SE_CSGO
hltvserver->StopRecording(NULL);
hltvserver->GetDemoRecorder()->StopRecording(NULL);
// TODO: Stop recording on all other active hltvservers (tv_stoprecord in csgo does this)
#else
demorecorder->StopRecording();
hltvserver->GetDemoRecorder()->StopRecording();
#endif
return 1;
@ -539,13 +530,10 @@ static cell_t Native_StopRecording(IPluginContext *pContext, const cell_t *param
// native bool:SourceTV_GetDemoFileName(String:sFilename[], maxlen);
static cell_t Native_GetDemoFileName(IPluginContext *pContext, const cell_t *params)
{
if (demorecorder == nullptr)
if (!hltvserver->GetDemoRecorder()->IsRecording())
return 0;
if (!demorecorder->IsRecording())
return 0;
char *pDemoFile = (char *)demorecorder->GetDemoFile();
char *pDemoFile = (char *)hltvserver->GetDemoRecorder()->GetDemoFile();
if (!pDemoFile)
return 0;
@ -557,13 +545,10 @@ static cell_t Native_GetDemoFileName(IPluginContext *pContext, const cell_t *par
// native SourceTV_GetRecordingTick();
static cell_t Native_GetRecordingTick(IPluginContext *pContext, const cell_t *params)
{
if (demorecorder == nullptr)
if (!hltvserver->GetDemoRecorder()->IsRecording())
return -1;
if (!demorecorder->IsRecording())
return -1;
return demorecorder->GetRecordingTick();
return hltvserver->GetDemoRecorder()->GetRecordingTick();
}
// native bool:SourceTV_PrintToDemoConsole(const String:format[], any:...);
@ -574,7 +559,7 @@ static cell_t Native_PrintToDemoConsole(IPluginContext *pContext, const cell_t *
if (!iserver)
return 0;
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot());
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVServer()->GetHLTVSlot());
if (!pClient)
return 0;
@ -742,6 +727,7 @@ const sp_nativeinfo_t sourcetv_natives[] =
{ "SourceTV_GetServerInstanceCount", Native_GetServerInstanceCount },
{ "SourceTV_SelectServerInstance", Native_SelectServerInstance },
{ "SourceTV_GetSelectedServerInstance", Native_GetSelectedServerInstance },
{ "SourceTV_IsActive", Native_IsActive },
{ "SourceTV_IsMasterProxy", Native_IsMasterProxy },
{ "SourceTV_GetServerIP", Native_GetServerIP },
{ "SourceTV_GetServerPort", Native_GetServerPort },

View File

@ -57,6 +57,16 @@ public bool:SourceTV_OnSpectatorPreConnect(const String:name[], String:password[
return true;
}
public SourceTV_OnServerStart(instance)
{
PrintToServer("SourceTV instance %d started.", instance);
}
public SourceTV_OnServerShutdown(instance)
{
PrintToServer("SourceTV instance %d shutdown.", instance);
}
public SourceTV_OnSpectatorConnected(client)
{
PrintToServer("SourceTV client %d connected. (isconnected %d)", client, SourceTV_IsClientConnected(client));

View File

@ -63,6 +63,12 @@
"linux" "65"
}
"CHLTVServer::Shutdown"
{
"windows" "45"
"linux" "46"
}
"CHLTVDirector::m_iPVSEntity"
{
"windows" "32"
@ -182,6 +188,12 @@
"linux" "56"
}
"CHLTVServer::Shutdown"
{
"windows" "41"
"linux" "42"
}
"CHLTVDirector::m_iPVSEntity"
{
"windows" "16"

View File

@ -35,12 +35,35 @@ native SourceTV_SelectServerInstance(instance);
*/
native SourceTV_GetSelectedServerInstance();
/**
* Called when a SourceTV is initialized.
*
* @param instance The SourceTV instance number.
* @noreturn
*/
forward SourceTV_OnServerStart(instance);
/**
* Called when a SourceTV server instance is shutdown.
*
* @param instance The SourceTV instance number.
* @noreturn
*/
forward SourceTV_OnServerShutdown(instance);
/**
* Returns whether this SourceTV instance is currently broadcasting.
*
* @return True if SourceTV instance is broadcasting, false otherwise.
*/
native bool:SourceTV_IsActive();
/**
* Returns whether this SourceTV instance is a master proxy or relay.
*
* @return True if SourceTV instance is master proxy, false otherwise.
*/
native SourceTV_IsMasterProxy();
native bool:SourceTV_IsMasterProxy();
/**
* Get the local ip of the SourceTV server.
@ -427,7 +450,10 @@ public __ext_stvmngr_SetNTVOptional()
MarkNativeAsOptional("SourceTV_GetServerInstanceCount");
MarkNativeAsOptional("SourceTV_SelectServerInstance");
MarkNativeAsOptional("SourceTV_GetSelectedServerInstance");
MarkNativeAsOptional("SourceTV_IsActive");
MarkNativeAsOptional("SourceTV_IsMasterProxy");
MarkNativeAsOptional("SourceTV_GetServerIP");
MarkNativeAsOptional("SourceTV_GetServerPort");
MarkNativeAsOptional("SourceTV_GetBotIndex");
MarkNativeAsOptional("SourceTV_GetLocalStats");
MarkNativeAsOptional("SourceTV_GetGlobalStats");