Allow printing to bot/demo console in CSS

CSS' CBaseClient::ClientPrintf tries to use the net channel directly
instead of going through the saner route using GameClient::SendNetMsg.

This introduces a stupid hack to work around the bots lack of a net
channel, so ClientPrintf's SVC_Print messages get included in the demo.
This commit is contained in:
Peace-Maker 2016-03-02 05:40:20 +01:00
parent 7e9a8d6aec
commit 357e1bc4c9
3 changed files with 111 additions and 3 deletions

View File

@ -31,6 +31,7 @@
#include "extension.h"
#include "forwards.h"
#include "inetmessage.h"
IHLTVDirector *hltvdirector = nullptr;
IHLTVServer *hltvserver = nullptr;
@ -47,11 +48,24 @@ ISDKTools *sdktools = nullptr;
IServer *iserver = nullptr;
IGameConfig *g_pGameConf = nullptr;
#if SOURCE_ENGINE != SE_CSGO
bool g_SendNetMsgHooked = false;
#endif
#if SOURCE_ENGINE == SE_CSGO
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 *);
@ -89,6 +103,34 @@ 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
sharesys->AddNatives(myself, sourcetv_natives);
sharesys->RegisterLibrary(myself, "sourcetvmanager");
@ -148,6 +190,13 @@ 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
gameconfs->CloseGameConfigFile(g_pGameConf);
@ -187,6 +236,9 @@ void SourceTVManager::HookSourceTVServer(IHLTVServer *hltv)
{
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
}
}
}
@ -205,6 +257,9 @@ void SourceTVManager::UnhookSourceTVServer(IHLTVServer *hltv)
{
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
}
}
}
@ -268,6 +323,45 @@ 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?
@ -293,9 +387,6 @@ bool SourceTVManager::OnHLTVBotExecuteStringCommand(const char *s)
if (!hltvserver || !iserver || !host_client)
RETURN_META_VALUE(MRES_IGNORED, 0);
if (*(void **)host_client)
RETURN_META_VALUE(MRES_IGNORED, 0);
IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot());
if (!pClient)
RETURN_META_VALUE(MRES_IGNORED, 0);

View File

@ -48,6 +48,8 @@
#include "ihltvdemorecorder.h"
#include "igameevents.h"
class INetMessage;
/**
* @brief Sample implementation of the SDK Extension.
* Note: Uncomment one of the pre-defined virtual functions in order to use it.
@ -133,6 +135,9 @@ 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);

View File

@ -79,6 +79,18 @@
}
"Offsets"
{
"CNetChan::SendNetMsg"
{
"windows" "40"
"linux" "41"
}
"CBaseClient::m_NetChannel"
{
"windows" "192"
"linux" "164"
}
"CBaseServer::BroadcastPrintf"
{
"windows" "35"