extensions/cstrike/natives.cpp
author Daniel Varnai <dvarnai@gmail.com>
Mon Aug 13 14:59:31 2012 -0400 (2012-08-13)
changeset 3663 2ff6e104c5b7
parent 3634 71cab87fc1b4
child 3702 a0256b609078
permissions -rw-r--r--
Added clan tag support for CS:GO in CStrike ext (bug 5429, r=psychonic).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Counter-Strike:Source 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 "RegNatives.h"
    34 #include "forwards.h"
    35 #include "util_cstrike.h"
    36 #include <server_class.h>
    37 
    38 int g_iPriceOffset = -1;
    39 
    40 #define REGISTER_NATIVE_ADDR(name, code) \
    41 	void *addr; \
    42 	if (!g_pGameConf->GetMemSig(name, &addr) || !addr) \
    43 	{ \
    44 		return pContext->ThrowNativeError("Failed to locate function"); \
    45 	} \
    46 	code; \
    47 	g_RegNatives.Register(pWrapper);
    48 
    49 inline CBaseEntity *GetCBaseEntity(int num, bool isplayer)
    50 {
    51 	edict_t *pEdict = gamehelpers->EdictOfIndex(num);
    52 	if (!pEdict || pEdict->IsFree())
    53 	{
    54 		return NULL;
    55 	}
    56 
    57 	if (num > 0 && num <= playerhelpers->GetMaxClients())
    58 	{
    59 		IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(pEdict);
    60 		if (!pPlayer || !pPlayer->IsConnected())
    61 		{
    62 			return NULL;
    63 		}
    64 	}
    65 	else if (isplayer) 
    66 	{
    67 		return NULL;
    68 	}
    69 
    70 	IServerUnknown *pUnk;
    71 	if ((pUnk=pEdict->GetUnknown()) == NULL)
    72 	{
    73 		return NULL;
    74 	}
    75 
    76 	return pUnk->GetBaseEntity();
    77 }
    78 
    79 bool UTIL_FindDataTable(SendTable *pTable, const char *name, sm_sendprop_info_t *info, unsigned int offset)
    80 {
    81 	const char *pname;
    82 	int props = pTable->GetNumProps();
    83 	SendProp *prop;
    84 	SendTable *table;
    85 
    86 	for (int i=0; i<props; i++)
    87 	{
    88 		prop = pTable->GetProp(i);
    89 
    90 		if ((table = prop->GetDataTable()) != NULL)
    91 		{
    92 			pname = table->GetName();
    93 			if (pname && strcmp(name, pname) == 0)
    94 			{
    95 				info->prop = prop;
    96 				info->actual_offset = offset + info->prop->GetOffset();
    97 				return true;
    98 			}
    99 
   100 			if (UTIL_FindDataTable(table, 
   101 				name,
   102 				info,
   103 				offset + prop->GetOffset())
   104 				)
   105 			{
   106 				return true;
   107 			}
   108 		}
   109 	}
   110 
   111 	return false;
   112 }
   113 
   114 //Taken From Sourcemod Core
   115 unsigned int strncopy(char *dest, const char *src, size_t count)
   116 {
   117 	if (!count)
   118 	{
   119 		return 0;
   120 	}
   121 
   122 	char *start = dest;
   123 	while ((*src) && (--count))
   124 	{
   125 		*dest++ = *src++;
   126 	}
   127 	*dest = '\0';
   128 
   129 	return (dest - start);
   130 }
   131 
   132 static cell_t CS_RespawnPlayer(IPluginContext *pContext, const cell_t *params)
   133 {
   134 	static ICallWrapper *pWrapper = NULL;
   135 	if (!pWrapper)
   136 	{
   137 		REGISTER_NATIVE_ADDR("RoundRespawn", 
   138 			pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, NULL, 0));
   139 	}
   140 
   141 	CBaseEntity *pEntity;
   142 	if (!(pEntity=GetCBaseEntity(params[1], true)))
   143 	{
   144 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   145 	}
   146 	pWrapper->Execute(&pEntity, NULL);
   147 
   148 	return 1;
   149 }
   150 
   151 static cell_t CS_SwitchTeam(IPluginContext *pContext, const cell_t *params)
   152 {
   153 	static ICallWrapper *pWrapper = NULL;
   154 	if (!pWrapper)
   155 	{
   156 		REGISTER_NATIVE_ADDR("SwitchTeam", 
   157 			PassInfo pass[1]; \
   158 			pass[0].flags = PASSFLAG_BYVAL; \
   159 			pass[0].size = sizeof(int); \
   160 			pass[0].type = PassType_Basic; \
   161 			pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 1))
   162 	}
   163 
   164 	CBaseEntity *pEntity;
   165 	if (!(pEntity=GetCBaseEntity(params[1], true)))
   166 	{
   167 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   168 	}
   169 
   170 	unsigned char vstk[sizeof(CBaseEntity *) + sizeof(int)];
   171 	unsigned char *vptr = vstk;
   172 
   173 	*(CBaseEntity **)vptr = pEntity;
   174 	vptr += sizeof(CBaseEntity *);
   175 	*(int *)vptr = params[2];
   176 	pWrapper->Execute(vstk, NULL);
   177 
   178 	return 1;
   179 }
   180 
   181 static cell_t CS_DropWeapon(IPluginContext *pContext, const cell_t *params)
   182 {
   183 	static ICallWrapper *pWrapper = NULL;
   184 	if (!pWrapper)
   185 	{
   186 		REGISTER_NATIVE_ADDR("CSWeaponDrop",
   187 			PassInfo pass[3]; \
   188 			pass[0].flags = PASSFLAG_BYVAL; \
   189 			pass[0].type  = PassType_Basic; \
   190 			pass[0].size  = sizeof(CBaseEntity *); \
   191 			pass[1].flags = PASSFLAG_BYVAL; \
   192 			pass[1].type  = PassType_Basic; \
   193 			pass[1].size  = sizeof(bool); \
   194 			pass[2].flags = PASSFLAG_BYVAL; \
   195 			pass[2].type  = PassType_Basic; \
   196 			pass[2].size  = sizeof(bool); \
   197 			pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 3))
   198 	}
   199 
   200 	CBaseEntity *pEntity;
   201 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   202 	{
   203 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   204 	}
   205 
   206 	CBaseEntity *pWeapon;
   207 	if (!(pWeapon = GetCBaseEntity(params[2], false)))
   208 	{
   209 		return pContext->ThrowNativeError("Weapon index %d is not valid", params[2]);
   210 	}
   211 
   212 	//Psychonic is awesome for this
   213 	sm_sendprop_info_t spi;
   214 	IServerUnknown *pUnk = (IServerUnknown *)pWeapon;
   215 	IServerNetworkable *pNet = pUnk->GetNetworkable();
   216 
   217 	if (!UTIL_FindDataTable(pNet->GetServerClass()->m_pTable, "DT_WeaponCSBase", &spi, 0))
   218 		return pContext->ThrowNativeError("Entity index %d is not a weapon", params[2]);
   219 
   220 	if (!gamehelpers->FindSendPropInfo("CBaseCombatWeapon", "m_hOwnerEntity", &spi))
   221 		return pContext->ThrowNativeError("Invalid entity index %d for weapon", params[2]);
   222 
   223 	CBaseHandle &hndl = *(CBaseHandle *)((uint8_t *)pWeapon + spi.actual_offset);
   224 	if (params[1] != hndl.GetEntryIndex())
   225 		return pContext->ThrowNativeError("Weapon %d is not owned by client %d", params[2], params[1]);
   226 
   227 	if (params[4] == 1 && g_pCSWeaponDropDetoured)
   228 		g_pIgnoreCSWeaponDropDetour = true;
   229 
   230 	unsigned char vstk[sizeof(CBaseEntity *) * 2 + sizeof(bool) * 2];
   231 	unsigned char *vptr = vstk;
   232 
   233 	// <psychonic> first one is always false. second is true to toss, false to just drop
   234 	*(CBaseEntity **)vptr = pEntity;
   235 	vptr += sizeof(CBaseEntity *);
   236 	*(CBaseEntity **)vptr = pWeapon;
   237 	vptr += sizeof(CBaseEntity *);
   238 	*(bool *)vptr = false;
   239 	vptr += sizeof(bool);
   240 	*(bool *)vptr = (params[3]) ? true : false;
   241 
   242  	pWrapper->Execute(vstk, NULL);
   243 
   244 	return 1;
   245 }
   246 
   247 static cell_t CS_TerminateRound(IPluginContext *pContext, const cell_t *params)
   248 {
   249 	if (g_pSDKTools == NULL)
   250 	{
   251 		smutils->LogError(myself, "SDKTools interface not found. TerminateRound native disabled.");
   252 	}
   253 	else if (g_pSDKTools->GetInterfaceVersion() < 2)
   254 	{
   255 		//<psychonic> THIS ISN'T DA LIMBO STICK. LOW IS BAD
   256 		return pContext->ThrowNativeError("SDKTools interface is outdated. TerminateRound native disabled.");
   257 	}
   258 
   259 	static ICallWrapper *pWrapper = NULL;
   260 
   261 	if (!pWrapper)
   262 	{
   263 		REGISTER_NATIVE_ADDR("TerminateRound",
   264 			PassInfo pass[2]; \
   265 			pass[0].flags = PASSFLAG_BYVAL; \
   266 			pass[0].type = PassType_Basic; \
   267 			pass[0].size = sizeof(float); \
   268 			pass[1].flags = PASSFLAG_BYVAL; \
   269 			pass[1].type = PassType_Basic; \
   270 			pass[1].size = sizeof(int); \
   271 			pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 2))
   272 	}
   273 
   274 	void *gamerules = g_pSDKTools->GetGameRules();
   275 	if (gamerules == NULL)
   276 	{
   277 		return pContext->ThrowNativeError("GameRules not available. TerminateRound native disabled.");
   278 	}
   279 
   280 	if (params[3] == 1 && g_pTerminateRoundDetoured)
   281 		g_pIgnoreTerminateDetour = true;
   282 
   283 	unsigned char vstk[sizeof(void *) + sizeof(float)+ sizeof(int)];
   284 	unsigned char *vptr = vstk;
   285 
   286 	*(void **)vptr = gamerules;
   287 	vptr += sizeof(void *);
   288 	*(float *)vptr = sp_ctof(params[1]);
   289 	vptr += sizeof(float);
   290 	*(int*)vptr = params[2];
   291 
   292 	pWrapper->Execute(vstk, NULL);
   293 
   294 	return 1;
   295 }
   296 
   297 static cell_t CS_GetTranslatedWeaponAlias(IPluginContext *pContext, const cell_t *params)
   298 {
   299 	char *weapon;
   300 	char *dest;
   301 
   302 	pContext->LocalToString(params[2], &dest);
   303 	pContext->LocalToString(params[1], &weapon);
   304 
   305 	const char *ret = GetTranslatedWeaponAlias(weapon);
   306 	strncopy(dest, ret, params[3]);
   307 	
   308 	return 1;
   309 }
   310 
   311 static cell_t CS_GetWeaponPrice(IPluginContext *pContext, const cell_t *params)
   312 {
   313 	//Hard code return values for weapons that dont call GetWeaponPrice and always use default value.
   314 	int id = GetRealWeaponID(params[2]);
   315 #if SOURCE_ENGINE == SE_CSGO
   316 	if (id == WEAPON_C4 || id == WEAPON_KNIFE || id == WEAPON_KNIFE_GG)
   317 #else
   318  	if (id == WEAPON_C4 || id == WEAPON_KNIFE || id == WEAPON_SHIELD)
   319 #endif
   320 		return 0;
   321 	if (id == WEAPON_KEVLAR)
   322 		return 650;
   323 	if (id == WEAPON_ASSAULTSUIT)
   324 		return 1000;
   325 	if (id == WEAPON_NIGHTVISION)
   326 		return 1250;
   327 #if SOURCE_ENGINE == SE_CSGO
   328 	if (id == WEAPON_DEFUSER)
   329 		return 200;
   330 #endif
   331 	if (id == 0)
   332 		return 0;
   333 
   334 	if (g_iPriceOffset == -1)
   335 	{
   336 		if (!g_pGameConf->GetOffset("WeaponPrice", &g_iPriceOffset))
   337 		{
   338 			g_iPriceOffset = -1;
   339 			return pContext->ThrowNativeError("Failed to get WeaponPrice offset");
   340 		}
   341 	}
   342 
   343 	CBaseEntity *pEntity;
   344 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   345 	{
   346 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   347 	}
   348 
   349 	void *info = GetWeaponInfo(id);
   350 	if (!info)
   351 		return pContext->ThrowNativeError("Failed to get weaponinfo");
   352 
   353 	int price = *(int *)((intptr_t)info + g_iPriceOffset);
   354 
   355 	if (params[3] || weaponNameOffset == -1)
   356 		return price;
   357 
   358 	const char *weapon_name = (const char *)((intptr_t)info + weaponNameOffset);
   359 
   360 #if SOURCE_ENGINE == SE_CSGO
   361 	return CallPriceForwardCSGO(params[1], weapon_name, price);
   362 #else
   363 	return CallPriceForward(params[1], weapon_name, price);
   364 #endif
   365 }
   366 
   367 static cell_t CS_GetClientClanTag(IPluginContext *pContext, const cell_t *params)
   368 {
   369 	static void *addr;
   370 	if (!addr)
   371 	{
   372 		if (!g_pGameConf->GetMemSig("SetClanTag", &addr) || !addr)
   373 		{
   374 			return pContext->ThrowNativeError("Failed to locate function");
   375 		}
   376 	}
   377 
   378 	CBaseEntity *pEntity;
   379 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   380 	{
   381 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   382 	}
   383 
   384 	static int tagOffsetOffset = -1;
   385 	static int tagOffset;
   386 
   387 	if (tagOffsetOffset == -1)
   388 	{
   389 		if (!g_pGameConf->GetOffset("ClanTagOffset", &tagOffsetOffset))
   390 		{
   391 			tagOffsetOffset = -1;
   392 			return pContext->ThrowNativeError("Unable to find ClanTagOffset gamedata");
   393 		}
   394 
   395 		tagOffset = *(int *)((intptr_t)addr + tagOffsetOffset);
   396 	}
   397 
   398 	size_t len;
   399 
   400 	const char *src = (char *)((intptr_t)pEntity + tagOffset);
   401 	pContext->StringToLocalUTF8(params[2], params[3], src, &len);
   402 
   403 	return len;
   404 }
   405 
   406 static cell_t CS_SetClientClanTag(IPluginContext *pContext, const cell_t *params)
   407 {
   408 	static ICallWrapper *pWrapper = NULL;
   409 
   410 	if (!pWrapper)
   411 	{
   412 		REGISTER_NATIVE_ADDR("SetClanTag",
   413 			PassInfo pass[2]; \
   414 			pass[0].flags = PASSFLAG_BYVAL; \
   415 			pass[0].type  = PassType_Basic; \
   416 			pass[0].size  = sizeof(CBaseEntity *); \
   417 			pass[1].flags = PASSFLAG_BYVAL; \
   418 			pass[1].type  = PassType_Basic; \
   419 			pass[1].size  = sizeof(char *); \
   420 			pWrapper = g_pBinTools->CreateCall(addr, CallConv_ThisCall, NULL, pass, 2))
   421 	}
   422 
   423 	CBaseEntity *pEntity;
   424 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   425 	{
   426 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   427 	}
   428 
   429 	char *szNewTag;
   430 	pContext->LocalToString(params[2], &szNewTag);
   431 
   432 	unsigned char vstk[sizeof(CBaseEntity *) + sizeof(char *)];
   433 	unsigned char *vptr = vstk;
   434 
   435 	*(CBaseEntity **)vptr = pEntity;
   436 	vptr += sizeof(CBaseEntity *);
   437 	*(char **)vptr = szNewTag;
   438 
   439 	pWrapper->Execute(vstk, NULL);
   440 
   441 	return 1;
   442 }
   443 
   444 static cell_t CS_AliasToWeaponID(IPluginContext *pContext, const cell_t *params)
   445 {
   446 	char *weapon;
   447 
   448 	pContext->LocalToString(params[1], &weapon);
   449 
   450 	return GetFakeWeaponID(AliasToWeaponID(weapon));
   451 }
   452 
   453 static cell_t CS_SetTeamScore(IPluginContext *pContext, const cell_t *params)
   454 {
   455 	if (g_pSDKTools == NULL)
   456 	{
   457 		smutils->LogError(myself, "SDKTools interface not found. CS_SetTeamScore native disabled.");
   458 	}
   459 	else if (g_pSDKTools->GetInterfaceVersion() < 2)
   460 	{
   461 		//<psychonic> THIS ISN'T DA LIMBO STICK. LOW IS BAD
   462 		return pContext->ThrowNativeError("SDKTools interface is outdated. CS_SetTeamScore native disabled.");
   463 	}
   464 
   465 	static void *addr;
   466 	if (!addr)
   467 	{
   468 		if (!g_pGameConf->GetMemSig("CheckWinLimit", &addr) || !addr)
   469 		{
   470 			return pContext->ThrowNativeError("Failed to locate CheckWinLimit function");
   471 		}
   472 	}
   473 
   474 	static int ctTeamOffsetOffset = -1;
   475 	static int ctTeamOffset;
   476 
   477 	static int tTeamOffsetOffset = -1;
   478 	static int tTeamOffset;
   479 
   480 	if (ctTeamOffsetOffset == -1)
   481 	{
   482 		if (!g_pGameConf->GetOffset("CTTeamScoreOffset", &ctTeamOffsetOffset))
   483 		{
   484 			ctTeamOffsetOffset = -1;
   485 			return pContext->ThrowNativeError("Unable to find CTTeamOffset gamedata");
   486 		}
   487 
   488 		ctTeamOffset = *(int *)((intptr_t)addr + ctTeamOffsetOffset);
   489 	}
   490 
   491 	if (tTeamOffsetOffset == -1)
   492 	{
   493 		if (!g_pGameConf->GetOffset("TTeamScoreOffset", &tTeamOffsetOffset))
   494 		{
   495 			tTeamOffsetOffset = -1;
   496 			return pContext->ThrowNativeError("Unable to find CTTeamOffset gamedata");
   497 		}
   498 
   499 		tTeamOffset = *(int *)((intptr_t)addr + tTeamOffsetOffset);
   500 	}
   501 
   502 	void *gamerules = g_pSDKTools->GetGameRules();
   503 	if (gamerules == NULL)
   504 	{
   505 		return pContext->ThrowNativeError("GameRules not available. CS_SetTeamScore native disabled.");
   506 	}
   507 
   508 	if (params[1] == 3)
   509 	{
   510 		*(int16_t *)((intptr_t)gamerules+ctTeamOffset) = params[2];
   511 		return 1;
   512 	}
   513 	else if (params[1] == 2)
   514 	{
   515 		*(int16_t *)((intptr_t)gamerules+tTeamOffset) = params[2];
   516 		return 1;
   517 	}
   518 	return pContext->ThrowNativeError("Invalid team index passed (%i).", params[1]);
   519 }
   520 
   521 static cell_t CS_GetTeamScore(IPluginContext *pContext, const cell_t *params)
   522 {
   523 	if (g_pSDKTools == NULL)
   524 	{
   525 		smutils->LogError(myself, "SDKTools interface not found. CS_GetTeamScore native disabled.");
   526 	}
   527 	else if (g_pSDKTools->GetInterfaceVersion() < 2)
   528 	{
   529 		//<psychonic> THIS ISN'T DA LIMBO STICK. LOW IS BAD
   530 		return pContext->ThrowNativeError("SDKTools interface is outdated. CS_GetTeamScore native disabled.");
   531 	}
   532 
   533 	static void *addr;
   534 	if (!addr)
   535 	{
   536 		if (!g_pGameConf->GetMemSig("CheckWinLimit", &addr) || !addr)
   537 		{
   538 			return pContext->ThrowNativeError("Failed to locate CheckWinLimit function");
   539 		}
   540 	}
   541 
   542 	static int ctTeamOffsetOffset = -1;
   543 	static int ctTeamOffset;
   544 
   545 	static int tTeamOffsetOffset = -1;
   546 	static int tTeamOffset;
   547 
   548 	if (ctTeamOffsetOffset == -1)
   549 	{
   550 		if (!g_pGameConf->GetOffset("CTTeamScoreOffset", &ctTeamOffsetOffset))
   551 		{
   552 			ctTeamOffsetOffset = -1;
   553 			return pContext->ThrowNativeError("Unable to find CTTeamOffset gamedata");
   554 		}
   555 
   556 		ctTeamOffset = *(int *)((intptr_t)addr + ctTeamOffsetOffset);
   557 	}
   558 
   559 	if (tTeamOffsetOffset == -1)
   560 	{
   561 		if (!g_pGameConf->GetOffset("TTeamScoreOffset", &tTeamOffsetOffset))
   562 		{
   563 			tTeamOffsetOffset = -1;
   564 			return pContext->ThrowNativeError("Unable to find CTTeamOffset gamedata");
   565 		}
   566 
   567 		tTeamOffset = *(int *)((intptr_t)addr + tTeamOffsetOffset);
   568 	}
   569 
   570 	void *gamerules = g_pSDKTools->GetGameRules();
   571 	if (gamerules == NULL)
   572 	{
   573 		return pContext->ThrowNativeError("GameRules not available. CS_GetTeamScore native disabled.");
   574 	}
   575 
   576 	if (params[1] == 3)
   577 	{
   578 		return *(int16_t *)((intptr_t)gamerules+ctTeamOffset);
   579 	}
   580 	else if (params[1] == 2)
   581 	{
   582 		return *(int16_t *)((intptr_t)gamerules+tTeamOffset);
   583 	}
   584 	return pContext->ThrowNativeError("Invalid team index passed (%i).", params[1]);
   585 }
   586 
   587 static cell_t CS_SetMVPCount(IPluginContext *pContext, const cell_t *params)
   588 {
   589 	static void *addr;
   590 	if (!addr)
   591 	{
   592 		if (!g_pGameConf->GetMemSig("IncrementNumMVPs", &addr) || !addr)
   593 		{
   594 			return pContext->ThrowNativeError("Failed to locate IncrementNumMVPs function");
   595 		}
   596 	}
   597 
   598 	CBaseEntity *pEntity;
   599 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   600 	{
   601 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   602 	}
   603 
   604 	static int mvpOffsetOffset = -1;
   605 	static int mvpOffset;
   606 
   607 	if (mvpOffsetOffset == -1)
   608 	{
   609 		if (!g_pGameConf->GetOffset("MVPCountOffset", &mvpOffsetOffset))
   610 		{
   611 			mvpOffsetOffset = -1;
   612 			return pContext->ThrowNativeError("Unable to find MVPCountOffset gamedata");
   613 		}
   614 
   615 		mvpOffset = *(int *)((intptr_t)addr + mvpOffsetOffset);
   616 	}
   617 
   618 	*(int *)((intptr_t)pEntity + mvpOffset) = params[2];
   619 
   620 	return 1;
   621 }
   622 
   623 static cell_t CS_GetMVPCount(IPluginContext *pContext, const cell_t *params)
   624 {
   625 	static void *addr;
   626 	if (!addr)
   627 	{
   628 		if (!g_pGameConf->GetMemSig("IncrementNumMVPs", &addr) || !addr)
   629 		{
   630 			return pContext->ThrowNativeError("Failed to locate IncrementNumMVPs function");
   631 		}
   632 	}
   633 
   634 	CBaseEntity *pEntity;
   635 	if (!(pEntity = GetCBaseEntity(params[1], true)))
   636 	{
   637 		return pContext->ThrowNativeError("Client index %d is not valid", params[1]);
   638 	}
   639 
   640 	static int mvpOffsetOffset = -1;
   641 	static int mvpOffset;
   642 
   643 	if (mvpOffsetOffset == -1)
   644 	{
   645 		if (!g_pGameConf->GetOffset("MVPCountOffset", &mvpOffsetOffset))
   646 		{
   647 			mvpOffsetOffset = -1;
   648 			return pContext->ThrowNativeError("Unable to find MVPCountOffset gamedata");
   649 		}
   650 
   651 		mvpOffset = *(int *)((intptr_t)addr + mvpOffsetOffset);
   652 	}
   653 
   654 	return *(int *)((intptr_t)pEntity + mvpOffset);
   655 }
   656 
   657 sp_nativeinfo_t g_CSNatives[] = 
   658 {
   659 	{"CS_RespawnPlayer",			CS_RespawnPlayer}, 
   660 	{"CS_SwitchTeam",				CS_SwitchTeam},
   661 	{"CS_DropWeapon",				CS_DropWeapon},
   662 	{"CS_TerminateRound",			CS_TerminateRound},
   663 	{"CS_GetTranslatedWeaponAlias",	CS_GetTranslatedWeaponAlias},
   664 	{"CS_GetWeaponPrice",			CS_GetWeaponPrice},
   665 	{"CS_GetClientClanTag",			CS_GetClientClanTag},
   666 	{"CS_SetClientClanTag",			CS_SetClientClanTag},
   667 	{"CS_AliasToWeaponID",			CS_AliasToWeaponID},
   668 	{"CS_GetTeamScore",				CS_GetTeamScore},
   669 	{"CS_SetTeamScore",				CS_SetTeamScore},
   670 	{"CS_GetMVPCount",				CS_GetMVPCount},
   671 	{"CS_SetMVPCount",				CS_SetMVPCount},
   672 	{NULL,							NULL}
   673 };
   674