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