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:
		
							parent
							
								
									fb03d5f2d5
								
							
						
					
					
						commit
						fabdbe7d12
					
				
							
								
								
									
										248
									
								
								extension.cpp
									
									
									
									
									
								
							
							
						
						
									
										248
									
								
								extension.cpp
									
									
									
									
									
								
							| @ -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); | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										15
									
								
								extension.h
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								extension.h
									
									
									
									
									
								
							| @ -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_
 | ||||
|  | ||||
							
								
								
									
										18
									
								
								forwards.cpp
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								forwards.cpp
									
									
									
									
									
								
							| @ -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) | ||||
| { | ||||
|  | ||||
| @ -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
									
								
							
							
						
						
									
										350
									
								
								hltvserverwrapper.cpp
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										95
									
								
								hltvserverwrapper.h
									
									
									
									
									
										Normal 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_
 | ||||
							
								
								
									
										80
									
								
								natives.cpp
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								natives.cpp
									
									
									
									
									
								
							| @ -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.
 | ||||
| // native SourceTV_IsActive();
 | ||||
| static cell_t Native_IsActive(IPluginContext *pContext, const cell_t *params) | ||||
| { | ||||
| 	if (hltvserver == nullptr) | ||||
| 		return 0; | ||||
| #endif | ||||
| 
 | ||||
| 	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 }, | ||||
|  | ||||
| @ -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)); | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -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"); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user