core/UserMessages.cpp
author Scott Ehlert <ds@alliedmods.net>
Fri Nov 14 09:18:30 2008 -0600 (2008-11-14)
changeset 2400 76b7b4575e21
parent 2289 44045ead61cd
child 2577 2c03ca54c114
permissions -rw-r--r--
Added preliminary support for Left 4 Dead; some things may not yet work.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "UserMessages.h"
    33 #include "sm_stringutil.h"
    34 
    35 UserMessages g_UserMsgs;
    36 
    37 #if SOURCE_ENGINE == SE_LEFT4DEAD
    38 SH_DECL_HOOK3(IVEngineServer, UserMessageBegin, SH_NOATTRIB, 0, bf_write *, IRecipientFilter *, int, const char *);
    39 #else
    40 SH_DECL_HOOK2(IVEngineServer, UserMessageBegin, SH_NOATTRIB, 0, bf_write *, IRecipientFilter *, int);
    41 #endif
    42 SH_DECL_HOOK0_void(IVEngineServer, MessageEnd, SH_NOATTRIB, 0);
    43 
    44 UserMessages::UserMessages() : m_InterceptBuffer(m_pBase, 2500)
    45 {
    46 	m_Names = sm_trie_create();
    47 	m_HookCount = 0;
    48 	m_InExec = false;
    49 	m_InHook = false;
    50 	m_CurFlags = 0;
    51 	m_CurId = INVALID_MESSAGE_ID;
    52 }
    53 
    54 UserMessages::~UserMessages()
    55 {
    56 	sm_trie_destroy(m_Names);
    57 
    58 	CStack<ListenerInfo *>::iterator iter;
    59 	for (iter=m_FreeListeners.begin(); iter!=m_FreeListeners.end(); iter++)
    60 	{
    61 		delete (*iter);
    62 	}
    63 	m_FreeListeners.popall();
    64 }
    65 
    66 void UserMessages::OnSourceModStartup(bool late)
    67 {
    68 	/* -1 means SourceMM was unable to get the user message list */
    69 	m_FallbackSearch = (g_SMAPI->GetUserMessageCount() == -1);
    70 }
    71 
    72 void UserMessages::OnSourceModAllInitialized()
    73 {
    74 	g_ShareSys.AddInterface(NULL, this);
    75 }
    76 
    77 void UserMessages::OnSourceModAllShutdown()
    78 {
    79 	if (m_HookCount)
    80 	{
    81 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Pre, false);
    82 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Post, true);
    83 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Pre, false);
    84 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Post, true);
    85 	}
    86 	m_HookCount = 0;
    87 }
    88 
    89 int UserMessages::GetMessageIndex(const char *msg)
    90 {
    91 	int msgid;
    92 
    93 	if (!sm_trie_retrieve(m_Names, msg, reinterpret_cast<void **>(&msgid)))
    94 	{
    95 		if (m_FallbackSearch)
    96 		{
    97 			char msgbuf[64];
    98 			int size;
    99 			msgid = 0;
   100 
   101 			while (gamedll->GetUserMessageInfo(msgid, msgbuf, sizeof(msgbuf), size))
   102 			{
   103 				if (strcmp(msgbuf, msg) == 0)
   104 				{
   105 					sm_trie_insert(m_Names, msg, reinterpret_cast<void *>(msgid));
   106 					return msgid;
   107 				}
   108 				msgid++;
   109 			}
   110 		}
   111 
   112 		msgid = g_SMAPI->FindUserMessage(msg);
   113 
   114 		if (msgid != INVALID_MESSAGE_ID)
   115 		{
   116 			sm_trie_insert(m_Names, msg, reinterpret_cast<void *>(msgid));
   117 		}
   118 	}
   119 
   120 	return msgid;
   121 }
   122 
   123 bool UserMessages::GetMessageName(int msgid, char *buffer, size_t maxlength) const
   124 {
   125 	if (m_FallbackSearch)
   126 	{
   127 		int size;
   128 		return gamedll->GetUserMessageInfo(msgid, buffer, maxlength, size);
   129 	}
   130 
   131 	const char *msg = g_SMAPI->GetUserMessage(msgid);
   132 
   133 	if (msg)
   134 	{
   135 		strncopy(buffer, msg, maxlength);
   136 		return true;
   137 	}
   138 
   139 	return false;
   140 }
   141 
   142 bf_write *UserMessages::StartMessage(int msg_id, const cell_t players[], unsigned int playersNum, int flags)
   143 {
   144 	bf_write *buffer;
   145 
   146 	if (m_InExec || m_InHook)
   147 	{
   148 		return NULL;
   149 	}
   150 	if (msg_id < 0 || msg_id >= 255)
   151 	{
   152 		return NULL;
   153 	}
   154 
   155 	m_CellRecFilter.Initialize(players, playersNum);
   156 
   157 	m_CurFlags = flags;
   158 	if (m_CurFlags & USERMSG_INITMSG)
   159 	{
   160 		m_CellRecFilter.SetToInit(true);
   161 	}
   162 	if (m_CurFlags & USERMSG_RELIABLE)
   163 	{
   164 		m_CellRecFilter.SetToReliable(true);
   165 	}
   166 
   167 	m_InExec = true;
   168 
   169 	if (m_CurFlags & USERMSG_BLOCKHOOKS)
   170 	{
   171 #if SOURCE_ENGINE == SE_LEFT4DEAD
   172 		buffer = ENGINE_CALL(UserMessageBegin)(static_cast<IRecipientFilter *>(&m_CellRecFilter), msg_id, g_SMAPI->GetUserMessage(msg_id));
   173 #else
   174 		buffer = ENGINE_CALL(UserMessageBegin)(static_cast<IRecipientFilter *>(&m_CellRecFilter), msg_id);
   175 #endif
   176 	} else {
   177 #if SOURCE_ENGINE == SE_LEFT4DEAD
   178 		buffer = engine->UserMessageBegin(static_cast<IRecipientFilter *>(&m_CellRecFilter), msg_id, g_SMAPI->GetUserMessage(msg_id));
   179 #else
   180 		buffer = engine->UserMessageBegin(static_cast<IRecipientFilter *>(&m_CellRecFilter), msg_id);
   181 #endif
   182 	}
   183 
   184 	return buffer;
   185 }
   186 
   187 bool UserMessages::EndMessage()
   188 {
   189 	if (!m_InExec)
   190 	{
   191 		return false;
   192 	}
   193 
   194 	if (m_CurFlags & USERMSG_BLOCKHOOKS)
   195 	{
   196 		ENGINE_CALL(MessageEnd)();
   197 	} else {
   198 		engine->MessageEnd();
   199 	}
   200 
   201 	m_InExec = false;
   202 	m_CurFlags = 0;
   203 	m_CellRecFilter.Reset();
   204 
   205 	return true;
   206 }
   207 
   208 bool UserMessages::HookUserMessage(int msg_id, IUserMessageListener *pListener, bool intercept)
   209 {
   210 	if (msg_id < 0 || msg_id >= 255)
   211 	{
   212 		return false;
   213 	}
   214 
   215 	ListenerInfo *pInfo;
   216 	if (m_FreeListeners.empty())
   217 	{
   218 		pInfo = new ListenerInfo;
   219 	} else {
   220 		pInfo = m_FreeListeners.front();
   221 		m_FreeListeners.pop();
   222 	}
   223 
   224 	pInfo->Callback = pListener;
   225 	pInfo->IsHooked = false;
   226 	pInfo->KillMe = false;
   227 
   228 	if (!m_HookCount++)
   229 	{
   230 		SH_ADD_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Pre, false);
   231 		SH_ADD_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Post, true);
   232 		SH_ADD_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Pre, false);
   233 		SH_ADD_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Post, true);
   234 	}
   235 
   236 	if (intercept)
   237 	{
   238 		m_msgIntercepts[msg_id].push_back(pInfo);
   239 	} else {
   240 		m_msgHooks[msg_id].push_back(pInfo);
   241 	}
   242 
   243 	return true;
   244 }
   245 
   246 bool UserMessages::UnhookUserMessage(int msg_id, IUserMessageListener *pListener, bool intercept)
   247 {	
   248 	MsgList *pList;
   249 	MsgIter iter;
   250 	ListenerInfo *pInfo;
   251 	bool deleted = false;
   252 
   253 	if (msg_id < 0 || msg_id >= 255)
   254 	{
   255 		return false;
   256 	}
   257 
   258 	pList = (intercept) ? &m_msgIntercepts[msg_id] : &m_msgHooks[msg_id];
   259 	for (iter=pList->begin(); iter!=pList->end(); iter++)
   260 	{
   261 		pInfo = (*iter);
   262 		if (pInfo->Callback == pListener)
   263 		{
   264 			if (pInfo->IsHooked)
   265 			{
   266 				pInfo->KillMe = true;
   267 				return true;
   268 			}
   269 			pList->erase(iter);
   270 			deleted = true;
   271 			break;
   272 		}
   273 	}
   274 
   275 	if (deleted)
   276 	{
   277 		_DecRefCounter();
   278 	}
   279 
   280 	return deleted;
   281 }
   282 
   283 void UserMessages::_DecRefCounter()
   284 {
   285 	if (--m_HookCount == 0)
   286 	{
   287 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Pre, false);
   288 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, UserMessageBegin, engine, this, &UserMessages::OnStartMessage_Post, true);
   289 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Pre, false);
   290 		SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, MessageEnd, engine, this, &UserMessages::OnMessageEnd_Post, true);
   291 	}
   292 }
   293 
   294 #if SOURCE_ENGINE == SE_LEFT4DEAD
   295 bf_write *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name)
   296 #else
   297 bf_write *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_type)
   298 #endif
   299 {
   300 	bool is_intercept_empty = m_msgIntercepts[msg_type].empty();
   301 	bool is_hook_empty = m_msgHooks[msg_type].empty();
   302 
   303 	if ((is_intercept_empty && is_hook_empty)
   304 		|| (m_InExec && (m_CurFlags & USERMSG_BLOCKHOOKS)))
   305 	{
   306 		m_InHook = false;
   307 		RETURN_META_VALUE(MRES_IGNORED, NULL);
   308 	}
   309 
   310 	m_CurId = msg_type;
   311 	m_CurRecFilter = filter;
   312 	m_InHook = true;
   313 	m_BlockEndPost = false;
   314 
   315 	if (!is_intercept_empty)
   316 	{
   317 		m_InterceptBuffer.Reset();
   318 		RETURN_META_VALUE(MRES_SUPERCEDE, &m_InterceptBuffer);
   319 	}
   320 
   321 	RETURN_META_VALUE(MRES_IGNORED, NULL);
   322 }
   323 
   324 #if SOURCE_ENGINE == SE_LEFT4DEAD
   325 bf_write *UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name)
   326 #else
   327 bf_write *UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_type)
   328 #endif
   329 {
   330 	if (!m_InHook)
   331 	{
   332 		RETURN_META_VALUE(MRES_IGNORED, NULL);
   333 	}
   334 
   335 	m_OrigBuffer = META_RESULT_ORIG_RET(bf_write *);
   336 
   337 	RETURN_META_VALUE(MRES_IGNORED, NULL);
   338 }
   339 
   340 void UserMessages::OnMessageEnd_Post()
   341 {
   342 	if (!m_InHook || m_BlockEndPost)
   343 	{
   344 		RETURN_META(MRES_IGNORED);
   345 	}
   346 
   347 	MsgList *pList;
   348 	MsgIter iter;
   349 	ListenerInfo *pInfo;
   350 
   351 	pList = &m_msgIntercepts[m_CurId];
   352 	for (iter=pList->begin(); iter!=pList->end(); )
   353 	{
   354 		pInfo = (*iter);
   355 		pInfo->IsHooked = true;
   356 		pInfo->Callback->OnUserMessageSent(m_CurId);
   357 
   358 		if (pInfo->KillMe)
   359 		{
   360 			iter = pList->erase(iter);
   361 			m_FreeListeners.push(pInfo);
   362 			_DecRefCounter();
   363 			continue;
   364 		}
   365 
   366 		pInfo->IsHooked = false;
   367 		iter++;
   368 	}
   369 
   370 	m_InHook = false;
   371 
   372 	pList = &m_msgHooks[m_CurId];
   373 	for (iter=pList->begin(); iter!=pList->end(); )
   374 	{
   375 		pInfo = (*iter);
   376 		pInfo->IsHooked = true;
   377 		pInfo->Callback->OnUserMessageSent(m_CurId);
   378 
   379 		if (pInfo->KillMe)
   380 		{
   381 			iter = pList->erase(iter);
   382 			m_FreeListeners.push(pInfo);
   383 			_DecRefCounter();
   384 			continue;
   385 		}
   386 
   387 		pInfo->IsHooked = false;
   388 		iter++;
   389 	}
   390 }
   391 
   392 void UserMessages::OnMessageEnd_Pre()
   393 {
   394 	if (!m_InHook)
   395 	{
   396 		RETURN_META(MRES_IGNORED);
   397 	}
   398 
   399 	MsgList *pList;
   400 	MsgIter iter;
   401 	ListenerInfo *pInfo;
   402 
   403 	ResultType res;
   404 	bool intercepted = false;
   405 	bool handled = false;
   406 
   407 	pList = &m_msgIntercepts[m_CurId];
   408 	for (iter=pList->begin(); iter!=pList->end(); )
   409 	{
   410 		pInfo = (*iter);
   411 		pInfo->IsHooked = true;
   412 		res = pInfo->Callback->InterceptUserMessage(m_CurId, &m_InterceptBuffer, m_CurRecFilter);
   413 
   414 		intercepted = true;
   415 
   416 		switch (res)
   417 		{
   418 		case Pl_Stop:
   419 			{
   420 				if (pInfo->KillMe)
   421 				{
   422 					pList->erase(iter);
   423 					m_FreeListeners.push(pInfo);
   424 					_DecRefCounter();
   425 					goto supercede;
   426 				}
   427 				pInfo->IsHooked = false;
   428 				goto supercede;
   429 			}
   430 		case Pl_Handled:
   431 			{
   432 				handled = true;
   433 				if (pInfo->KillMe)
   434 				{
   435 					iter = pList->erase(iter);
   436 					m_FreeListeners.push(pInfo);
   437 					_DecRefCounter();
   438 					continue;
   439 				}
   440 				break;
   441 			}
   442 		default:
   443 			{
   444 				if (pInfo->KillMe)
   445 				{
   446 					iter = pList->erase(iter);
   447 					m_FreeListeners.push(pInfo);
   448 					_DecRefCounter();
   449 					continue;
   450 				}
   451 				break;
   452 			}
   453 		}
   454 		pInfo->IsHooked = false;
   455 		iter++;
   456 	}
   457 
   458 	if (handled)
   459 	{
   460 		goto supercede;
   461 	}
   462 
   463 	if (intercepted)
   464 	{
   465 		bf_write *engine_bfw;
   466 
   467 #if SOURCE_ENGINE == SE_LEFT4DEAD
   468 		engine_bfw = ENGINE_CALL(UserMessageBegin)(m_CurRecFilter, m_CurId, g_SMAPI->GetUserMessage(m_CurId));
   469 #else
   470 		engine_bfw = ENGINE_CALL(UserMessageBegin)(m_CurRecFilter, m_CurId);
   471 #endif
   472 		m_ReadBuffer.StartReading(m_InterceptBuffer.GetBasePointer(), m_InterceptBuffer.GetNumBytesWritten());
   473 		engine_bfw->WriteBitsFromBuffer(&m_ReadBuffer, m_InterceptBuffer.GetNumBitsWritten());
   474 		ENGINE_CALL(MessageEnd)();
   475 	}
   476 
   477 	pList = &m_msgHooks[m_CurId];
   478 	for (iter=pList->begin(); iter!=pList->end(); )
   479 	{
   480 		pInfo = (*iter);
   481 		pInfo->IsHooked = true;
   482 		pInfo->Callback->OnUserMessage(m_CurId, m_OrigBuffer, m_CurRecFilter);
   483 
   484 		if (pInfo->KillMe)
   485 		{
   486 			iter = pList->erase(iter);
   487 			m_FreeListeners.push(pInfo);
   488 			_DecRefCounter();
   489 			continue;
   490 		}
   491 
   492 		pInfo->IsHooked = false;
   493 		iter++;
   494 	}
   495 
   496 	RETURN_META((intercepted) ? MRES_SUPERCEDE : MRES_IGNORED);
   497 supercede:
   498 	m_InHook = false;
   499 	m_BlockEndPost = true;
   500 	RETURN_META(MRES_SUPERCEDE);
   501 }