extensions/sdktools/output.cpp
author David Anderson <dvander@alliedmods.net>
Fri Jul 11 09:24:12 2008 +0000 (2008-07-11)
changeset 2195 5b70179559f9
parent 2122 2c31629b07f4
child 2400 76b7b4575e21
permissions -rw-r--r--
fixed regressions in debug mode
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod SDKTools Extension
     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 "extension.h"
    33 #include "output.h"
    34 
    35 ISourcePawnEngine *spengine = NULL;
    36 EntityOutputManager g_OutputManager;
    37 
    38 EntityOutputManager::EntityOutputManager()
    39 {
    40 	info_address = NULL;
    41 	info_callback = NULL;
    42 	HookCount = 0;
    43 	is_detoured = false;
    44 	enabled = false;
    45 }
    46 
    47 EntityOutputManager::~EntityOutputManager()
    48 {
    49 	if (!enabled)
    50 	{
    51 		return;
    52 	}
    53 
    54 	EntityOutputs->Destroy();
    55 	ClassNames->Destroy();
    56 	ShutdownFireEventDetour();
    57 }
    58 
    59 void EntityOutputManager::Init()
    60 {
    61 	enabled = CreateFireEventDetour();
    62 
    63 	if (!enabled)
    64 	{
    65 		return;
    66 	}
    67 
    68 	EntityOutputs = adtfactory->CreateBasicTrie();
    69 	ClassNames = adtfactory->CreateBasicTrie();
    70 }
    71 
    72 bool EntityOutputManager::IsEnabled()
    73 {
    74 	return enabled;
    75 }
    76 
    77 bool EntityOutputManager::CreateFireEventDetour()
    78 {
    79 	if (!g_pGameConf->GetMemSig("FireOutput", &info_address))
    80 	{
    81 		return false;
    82 	}
    83 
    84 	if (!info_address)
    85 	{
    86 		g_pSM->LogError(myself, "Could not locate FireOutput - Disabling Entity Outputs");
    87 		return false;
    88 	}
    89 
    90 	if (!g_pGameConf->GetOffset("FireOutputBackup", (int *)&(info_restore.bytes)))
    91 	{
    92 		return false;
    93 	}
    94 
    95 	/* First, save restore bits */
    96 	for (size_t i=0; i<info_restore.bytes; i++)
    97 	{
    98 		info_restore.patch[i] = ((unsigned char *)info_address)[i];
    99 	}
   100 
   101 	info_callback = spengine->ExecAlloc(100);
   102 	JitWriter wr;
   103 	JitWriter *jit = &wr;
   104 	wr.outbase = (jitcode_t)info_callback;
   105 	wr.outptr = wr.outbase;
   106 
   107 	/* Function we are detouring into is
   108 	 *
   109 	 * void FireEventDetour(CBaseEntityOutput(void *) *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay = 0 )
   110 	 */
   111 
   112 	/* push		fDelay [esp+20h]
   113 	 * push		pCaller [esp+1Ch]
   114 	 * push		pActivator [esp+18h]
   115 	 * push		pOutput [ecx]
   116 	 */
   117 
   118 
   119 #if defined PLATFORM_WINDOWS
   120 
   121 	IA32_Push_Rm_Disp8_ESP(jit, 32);
   122 	IA32_Push_Rm_Disp8_ESP(jit, 32);
   123 	IA32_Push_Rm_Disp8_ESP(jit, 32);
   124 
   125 	IA32_Push_Reg(jit, REG_ECX);
   126 
   127 #elif defined PLATFORM_LINUX
   128 	IA32_Push_Rm_Disp8_ESP(jit, 20);
   129 	IA32_Push_Rm_Disp8_ESP(jit, 20);
   130 	IA32_Push_Rm_Disp8_ESP(jit, 20);
   131 
   132 	IA32_Push_Rm_Disp8_ESP(jit, 16);
   133 #endif
   134 
   135 	jitoffs_t call = IA32_Call_Imm32(jit, 0); 
   136 	IA32_Write_Jump32_Abs(jit, call, (void *)TempDetour);
   137 
   138 	
   139 #if defined PLATFORM_LINUX
   140 	IA32_Add_Rm_Imm8(jit, REG_ESP, 4, MOD_REG);		//add esp, 4
   141 #elif defined PLATFORM_WINDOWS
   142 	IA32_Pop_Reg(jit, REG_ECX);
   143 #endif
   144 
   145 	IA32_Add_Rm_Imm8(jit, REG_ESP, 12, MOD_REG);	//add esp, 12 (0Ch)
   146 
   147 
   148 	/* Patch old bytes in */
   149 	for (size_t i=0; i<info_restore.bytes; i++)
   150 	{
   151 		jit->write_ubyte(info_restore.patch[i]);
   152 	}
   153 
   154 	/* Return to the original function */
   155 	call = IA32_Jump_Imm32(jit, 0);
   156 	IA32_Write_Jump32_Abs(jit, call, (unsigned char *)info_address + info_restore.bytes);
   157 
   158 	return true;
   159 }
   160 
   161 void EntityOutputManager::InitFireEventDetour()
   162 {
   163 	if (!is_detoured)
   164 	{
   165 		DoGatePatch((unsigned char *)info_address, &info_callback);
   166 		is_detoured = true;
   167 	}
   168 }
   169 
   170 void EntityOutputManager::DeleteFireEventDetour()
   171 {
   172 	if (is_detoured)
   173 	{
   174 		ShutdownFireEventDetour();
   175 	}
   176 
   177 	if (info_callback)
   178 	{
   179 		/* Free the gate */
   180 		spengine->ExecFree(info_callback);
   181 		info_callback = NULL;
   182 	}
   183 }
   184 
   185 void TempDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay)
   186 {
   187 	g_OutputManager.FireEventDetour(pOutput, pActivator, pCaller, fDelay);
   188 }
   189 
   190 void EntityOutputManager::ShutdownFireEventDetour()
   191 {
   192 	if (info_callback)
   193 	{
   194 		/* Remove the patch */
   195 		ApplyPatch(info_address, 0, &info_restore, NULL);
   196 		is_detoured = false;
   197 	}
   198 }
   199 
   200 void EntityOutputManager::FireEventDetour(void *pOutput, CBaseEntity *pActivator, CBaseEntity *pCaller, float fDelay)
   201 {
   202 	char sOutput[20];
   203 	Q_snprintf(sOutput, sizeof(sOutput), "%x", pOutput);
   204 
   205 	// attempt to directly lookup a hook using the pOutput pointer
   206 	OutputNameStruct *pOutputName = NULL;
   207 
   208 	edict_t *pEdict = gameents->BaseEntityToEdict(pCaller);
   209 
   210 	/* TODO: Add support for entities without an edict */
   211 	if (pEdict == NULL)
   212 	{
   213 		return;
   214 	}
   215 
   216 	bool fastLookup = false;
   217 	
   218 	// Fast lookup failed - check the slow way for hooks that havn't fired yet
   219 	if ((fastLookup = EntityOutputs->Retrieve(sOutput, (void **)&pOutputName)) == false)
   220 	{
   221 		const char *classname = pEdict->GetClassName();
   222 		const char *outputname = FindOutputName(pOutput, pCaller);
   223 
   224 		pOutputName = FindOutputPointer(classname, outputname, false);
   225 
   226 		if (!pOutputName)
   227 		{
   228 			return;
   229 		}
   230 	}
   231 
   232 	if (!pOutputName->hooks.empty())
   233 	{
   234 		if (!fastLookup)
   235 		{
   236 			// hook exists on this classname and output - map it into our quick find trie
   237 			EntityOutputs->Insert(sOutput, pOutputName);
   238 		}
   239 
   240 		SourceHook::List<omg_hooks *>::iterator _iter;
   241 
   242 		omg_hooks *hook;
   243 
   244 		_iter = pOutputName->hooks.begin();
   245 
   246 		while (_iter != pOutputName->hooks.end())
   247 		{
   248 			hook = (omg_hooks *)*_iter;
   249 
   250 			hook->in_use = true;
   251 
   252 			int serial = pEdict->m_NetworkSerialNumber;
   253 			
   254 			if (serial != hook->entity_filter && hook->entity_index == engine->IndexOfEdict(pEdict))
   255 			{
   256 				// same entity index but different serial number. Entity has changed, kill the hook.
   257 				_iter = pOutputName->hooks.erase(_iter);
   258 				CleanUpHook(hook);
   259 
   260 				continue;
   261 			}
   262 
   263 			if (hook->entity_filter == -1 || hook->entity_filter == serial) // Global classname hook
   264 			{
   265 				//fire the forward to hook->pf
   266 				hook->pf->PushString(pOutputName->Name);
   267 				hook->pf->PushCell(engine->IndexOfEdict(pEdict));
   268 				
   269 				edict_t *pEdictActivator = gameents->BaseEntityToEdict(pActivator);
   270 				if (!pEdictActivator)
   271 				{
   272 					hook->pf->PushCell(-1);
   273 				}
   274 				else
   275 				{
   276 					hook->pf->PushCell(engine->IndexOfEdict(pEdictActivator));
   277 				}
   278 				//hook->pf->PushCell(handle);
   279 				hook->pf->PushFloat(fDelay);
   280 				hook->pf->Execute(NULL);
   281 
   282 				if ((hook->entity_filter != -1) && hook->only_once)
   283 				{
   284 					_iter = pOutputName->hooks.erase(_iter);
   285 					CleanUpHook(hook);
   286 
   287 					continue;
   288 				}
   289 
   290 				if (hook->delete_me)
   291 				{
   292 					_iter = pOutputName->hooks.erase(_iter);
   293 					CleanUpHook(hook);
   294 					continue;
   295 				}
   296 			}
   297 
   298 			hook->in_use = false;
   299 			_iter++;
   300 		}
   301 	}
   302 }
   303 
   304 omg_hooks *EntityOutputManager::NewHook()
   305 {
   306 	omg_hooks *hook;
   307 
   308 	if (FreeHooks.empty())
   309 	{
   310 		hook = new omg_hooks;
   311 	}
   312 	else
   313 	{
   314 		hook = g_OutputManager.FreeHooks.front();
   315 		g_OutputManager.FreeHooks.pop();
   316 	}
   317 
   318 	return hook;
   319 }
   320 
   321 void EntityOutputManager::OnHookAdded()
   322 {
   323 	HookCount++;
   324 
   325 	if (HookCount == 1)
   326 	{
   327 		// This is the first hook created
   328 		InitFireEventDetour();
   329 	}
   330 }
   331 
   332 void EntityOutputManager::OnHookRemoved()
   333 {
   334 	HookCount--;
   335 
   336 	if (HookCount == 0)
   337 	{
   338 		ShutdownFireEventDetour();
   339 	}
   340 }
   341 
   342 void EntityOutputManager::CleanUpHook(omg_hooks *hook)
   343 {
   344 	FreeHooks.push(hook);
   345 
   346 	OnHookRemoved();
   347 
   348 	IPlugin *pPlugin = plsys->FindPluginByContext(hook->pf->GetParentContext()->GetContext());
   349 	SourceHook::List<omg_hooks *> *pList = NULL;
   350 
   351 	if (!pPlugin->GetProperty("OutputHookList", (void **)&pList, false) || !pList)
   352 	{
   353 		return;
   354 	}
   355 
   356 	SourceHook::List<omg_hooks *>::iterator p_iter = pList->begin();
   357 
   358 	omg_hooks *pluginHook;
   359 
   360 	while (p_iter != pList->end())
   361 	{
   362 		pluginHook = (omg_hooks *)*p_iter;
   363 		if (pluginHook == hook)
   364 		{
   365 			p_iter = pList->erase(p_iter);
   366 		}
   367 		else
   368 		{
   369 			p_iter++;
   370 		}
   371 	}
   372 }
   373 
   374 void EntityOutputManager::OnPluginDestroyed(IPlugin *plugin)
   375 {
   376 	SourceHook::List<omg_hooks *> *pList = NULL;
   377 
   378 	if (plugin->GetProperty("OutputHookList", (void **)&pList, true))
   379 	{
   380 		SourceHook::List<omg_hooks *>::iterator p_iter = pList->begin();
   381 		omg_hooks *hook;
   382 
   383 		while (p_iter != pList->end())
   384 		{
   385 			hook = (omg_hooks *)*p_iter;
   386 		
   387 			p_iter = pList->erase(p_iter); //remove from this plugins list
   388 			hook->m_parent->hooks.remove(hook); // remove from the y's list
   389 
   390 			FreeHooks.push(hook); //save the omg_hook
   391 
   392 			OnHookRemoved();
   393 		}		
   394 	}
   395 }
   396 
   397 OutputNameStruct *EntityOutputManager::FindOutputPointer(const char *classname, const char *outputname, bool create)
   398 {
   399 	ClassNameStruct *pClassname;
   400 	
   401 	if (!ClassNames->Retrieve(classname, (void **)&pClassname))
   402 	{
   403 		if (create)
   404 		{
   405 			pClassname = new ClassNameStruct;
   406 			ClassNames->Insert(classname, pClassname);
   407 		}
   408 		else
   409 		{
   410 			return NULL;
   411 		}
   412 	}
   413 
   414 	OutputNameStruct *pOutputName;
   415 
   416 	if (!pClassname->OutputList->Retrieve(outputname, (void **)&pOutputName))
   417 	{
   418 		if (create)
   419 		{
   420 			pOutputName = new OutputNameStruct;
   421 			pClassname->OutputList->Insert(outputname, pOutputName);
   422 			strncpy(pOutputName->Name, outputname, sizeof(pOutputName->Name));
   423 			pOutputName->Name[49] = 0;
   424 		}
   425 		else
   426 		{
   427 			return NULL;
   428 		}
   429 	}
   430 
   431 	return pOutputName;
   432 }
   433 
   434 // Iterate the datamap of pCaller and look for output pointers with the same address as pOutput
   435 const char *EntityOutputManager::FindOutputName(void *pOutput, CBaseEntity *pCaller)
   436 {
   437 	datamap_t *pMap = gamehelpers->GetDataMap(pCaller);
   438 
   439 	while (pMap)
   440 	{
   441 		for (int i=0; i<pMap->dataNumFields; i++)
   442 		{
   443 			if (pMap->dataDesc[i].flags & FTYPEDESC_OUTPUT)
   444 			{
   445 				if ((char *)pCaller + pMap->dataDesc[i].fieldOffset[0] == pOutput)
   446 				{
   447 					return pMap->dataDesc[i].externalName;
   448 				}
   449 			}
   450 		}
   451 		pMap = pMap->baseMap;
   452 	}
   453 
   454 	return NULL;
   455 }
   456 
   457 // Thanks SM core
   458 edict_t *EntityOutputManager::BaseHandleToEdict(CBaseHandle &hndl)
   459 {
   460 	if (!hndl.IsValid())
   461 	{
   462 		return NULL;
   463 	}
   464 
   465 	int index = hndl.GetEntryIndex();
   466 
   467 	edict_t *pStoredEdict;
   468 
   469 	pStoredEdict = engine->PEntityOfEntIndex(index);
   470 
   471 	if (pStoredEdict == NULL)
   472 	{
   473 		return NULL;
   474 	}
   475 
   476 	IServerEntity *pSE = pStoredEdict->GetIServerEntity();
   477 
   478 	if (pSE == NULL)
   479 	{
   480 		return NULL;
   481 	}
   482 
   483 	if (pSE->GetRefEHandle() != hndl)
   484 	{
   485 		return NULL;
   486 	}
   487 
   488 	return pStoredEdict;
   489 }