From 357e1bc4c99898f678aee8f6480f8528490705ad Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Wed, 2 Mar 2016 05:40:20 +0100 Subject: [PATCH] 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. --- extension.cpp | 97 +++++++++++++++++++++++++++++++++++++-- extension.h | 5 ++ sourcetvmanager.games.txt | 12 +++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/extension.cpp b/extension.cpp index 0ba2daa..65ad286 100644 --- a/extension.cpp +++ b/extension.cpp @@ -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); diff --git a/extension.h b/extension.h index e7521f5..437cdd1 100644 --- a/extension.h +++ b/extension.h @@ -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); diff --git a/sourcetvmanager.games.txt b/sourcetvmanager.games.txt index c423ad6..785ce96 100644 --- a/sourcetvmanager.games.txt +++ b/sourcetvmanager.games.txt @@ -79,6 +79,18 @@ } "Offsets" { + "CNetChan::SendNetMsg" + { + "windows" "40" + "linux" "41" + } + + "CBaseClient::m_NetChannel" + { + "windows" "192" + "linux" "164" + } + "CBaseServer::BroadcastPrintf" { "windows" "35"