extensions/clientprefs/extension.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Tue Jul 03 07:51:12 2012 -0400 (2012-07-03)
changeset 3573 440c51204096
parent 3500 87231551b8e6
child 3630 b72450a8be42
permissions -rw-r--r--
Fixed clientprefs prefab menu double free crash (bug 5374, r=asherkin).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Client Preferences 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 <sourcemod_version.h>
    33 #include "extension.h"
    34 
    35 /**
    36  * @file extension.cpp
    37  * @brief Implement extension code here.
    38  */
    39 
    40 ClientPrefs g_ClientPrefs;		/**< Global singleton for extension's main interface */
    41 
    42 SMEXT_LINK(&g_ClientPrefs);
    43 
    44 HandleType_t g_CookieType = 0;
    45 CookieTypeHandler g_CookieTypeHandler;
    46 
    47 HandleType_t g_CookieIterator = 0;
    48 CookieIteratorHandler g_CookieIteratorHandler;
    49 DbDriver g_DriverType;
    50 static const DatabaseInfo *storage_local = NULL;
    51 
    52 bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late)
    53 {
    54 	queryMutex = threader->MakeMutex();
    55 	cookieMutex = threader->MakeMutex();
    56 
    57 	DBInfo = dbi->FindDatabaseConf("clientprefs");
    58 
    59 	if (DBInfo == NULL)
    60 	{
    61 		DBInfo = dbi->FindDatabaseConf("default");
    62 
    63 		if (DBInfo == NULL ||
    64 			(strcmp(DBInfo->host, "localhost") == 0 &&
    65 			 strcmp(DBInfo->database, "sourcemod") == 0 && 
    66 			 strcmp(DBInfo->user, "root") == 0 &&
    67 			 strcmp(DBInfo->pass, "") == 0 &&
    68 			 strcmp(DBInfo->driver, "") == 0))
    69 		{
    70 			storage_local = dbi->FindDatabaseConf("storage-local");
    71 			if (DBInfo == NULL)
    72 			{
    73 				DBInfo = storage_local;
    74 			}
    75 		}
    76 
    77 		if (DBInfo == NULL)
    78 		{
    79 			snprintf(error, maxlength, "Could not find \"clientprefs\" or \"default\" database configs");
    80 			return false;
    81 		}
    82 	}
    83 
    84 	if (DBInfo->driver[0] != '\0')
    85 	{
    86 		Driver = dbi->FindOrLoadDriver(DBInfo->driver);
    87 	}
    88 	else
    89 	{
    90 		Driver = dbi->GetDefaultDriver();
    91 	}
    92 
    93 	if (Driver == NULL)
    94 	{
    95 		snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver);
    96 		return false;
    97 	}
    98 
    99 	Database = NULL;
   100 	databaseLoading = true;
   101 	TQueryOp *op = new TQueryOp(Query_Connect, 0);
   102 	dbi->AddToThreadQueue(op, PrioQueue_High);
   103 
   104 	dbi->AddDependency(myself, Driver);
   105 
   106 	sharesys->AddNatives(myself, g_ClientPrefNatives);
   107 	sharesys->RegisterLibrary(myself, "clientprefs");
   108 	identity = sharesys->CreateIdentity(sharesys->CreateIdentType("ClientPrefs"), this);
   109 	g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell);
   110 
   111 	g_CookieType = handlesys->CreateType("Cookie", 
   112 		&g_CookieTypeHandler, 
   113 		0, 
   114 		NULL, 
   115 		NULL, 
   116 		myself->GetIdentity(), 
   117 		NULL);
   118 
   119 	g_CookieIterator = handlesys->CreateType("CookieIterator", 
   120 		&g_CookieIteratorHandler, 
   121 		0, 
   122 		NULL, 
   123 		NULL, 
   124 		myself->GetIdentity(), 
   125 		NULL);
   126 
   127 	IMenuStyle *style = menus->GetDefaultStyle();
   128 	g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, identity);
   129 	g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:");
   130 
   131 	plsys->AddPluginsListener(&g_CookieManager);
   132 
   133 	phrases = translator->CreatePhraseCollection();
   134 	phrases->AddPhraseFile("clientprefs.phrases");
   135 	phrases->AddPhraseFile("common.phrases");
   136 
   137 	if (late)
   138 	{
   139 		int maxclients = playerhelpers->GetMaxClients();
   140 
   141 		for (int i = 1; i <= maxclients; i++)
   142 		{
   143 			IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(i);
   144 
   145 			if (!pPlayer || !pPlayer->IsAuthorized())
   146 			{
   147 				continue;
   148 			}
   149 
   150 			g_CookieManager.OnClientAuthorized(i, pPlayer->GetAuthString());
   151 		}
   152 	}
   153 
   154 	return true;
   155 }
   156 
   157 void ClientPrefs::SDK_OnAllLoaded()
   158 {
   159 	playerhelpers->AddClientListener(&g_CookieManager);
   160 }
   161 
   162 bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface)
   163 {
   164 	if ((void *)pInterface == (void *)(Database->GetDriver()))
   165 	{
   166 		return false;
   167 	}
   168 
   169 	return true;
   170 }
   171 
   172 void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface)
   173 {
   174 	if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver()))
   175 	{
   176 		Database->Close();
   177 		Database = NULL;
   178 	}
   179 }
   180 
   181 void ClientPrefs::SDK_OnUnload()
   182 {
   183 	handlesys->RemoveType(g_CookieType, myself->GetIdentity());
   184 	handlesys->RemoveType(g_CookieIterator, myself->GetIdentity());
   185 
   186 	g_CookieManager.Unload();
   187 
   188 	if (Database != NULL)
   189 	{
   190 		Database->Close();
   191 	}
   192 
   193 	forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward);
   194 
   195 	HandleSecurity sec = HandleSecurity(identity, identity);
   196 	HandleError err = handlesys->FreeHandle(g_CookieManager.clientMenu->GetHandle(), &sec);
   197 	if (HandleError_None != err)
   198 	{
   199 		g_pSM->LogError(myself, "Error %d when attempting to free client menu handle", err);
   200 	}
   201 
   202 	phrases->Destroy();
   203 
   204 	sharesys->DestroyIdentity( identity );
   205 
   206 	plsys->RemovePluginsListener(&g_CookieManager);
   207 	playerhelpers->RemoveClientListener(&g_CookieManager);
   208 
   209 	queryMutex->DestroyThis();
   210 	cookieMutex->DestroyThis();
   211 }
   212 
   213 void ClientPrefs::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax)
   214 {
   215 	if (Database == NULL && !databaseLoading)
   216 	{
   217 		g_pSM->LogMessage(myself, "Attempting to reconnect to database...");
   218 		
   219 		databaseLoading = true;
   220 		
   221 		TQueryOp *op = new TQueryOp(Query_Connect, 0);
   222 		dbi->AddToThreadQueue(op, PrioQueue_High);
   223 	}
   224 }
   225 
   226 void ClientPrefs::DatabaseConnect()
   227 {
   228 	char error[256];
   229 	int errCode = 0;
   230 
   231 	Database = Driver->Connect(DBInfo, true, error, sizeof(error));
   232 
   233 	if (Database == NULL &&
   234 		DBInfo != storage_local &&
   235 		storage_local != NULL)
   236 	{
   237 		DBInfo = storage_local;
   238 		Database = Driver->Connect(DBInfo, true, error, sizeof(error));
   239 	}
   240 
   241 	if (Database == NULL)
   242 	{
   243 		g_pSM->LogError(myself, error);
   244 		databaseLoading = false;
   245 		ProcessQueryCache();
   246 		return;
   247 	}
   248 
   249 	const char *identifier = Driver->GetIdentifier();
   250 
   251 	if (strcmp(identifier, "sqlite") == 0)
   252 	{
   253 		g_DriverType = Driver_SQLite;
   254 
   255 		if (!Database->DoSimpleQuery(
   256 				"CREATE TABLE IF NOT EXISTS sm_cookies  \
   257 				( \
   258 					id INTEGER PRIMARY KEY AUTOINCREMENT, \
   259 					name varchar(30) NOT NULL UNIQUE, \
   260 					description varchar(255), \
   261 					access INTEGER \
   262 				)"))
   263 		{
   264 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
   265 			goto fatal_fail;
   266 		}
   267 
   268 		if (!Database->DoSimpleQuery(
   269 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   270 				( \
   271 					player varchar(65) NOT NULL, \
   272 					cookie_id int(10) NOT NULL, \
   273 					value varchar(100), \
   274 					timestamp int, \
   275 					PRIMARY KEY (player, cookie_id) \
   276 				)"))
   277 		{
   278 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
   279 			goto fatal_fail;
   280 		}
   281 	}
   282 	else if (strcmp(identifier, "mysql") == 0)
   283 	{
   284 		g_DriverType = Driver_MySQL;
   285 
   286 		if (!Database->DoSimpleQuery(
   287 				"CREATE TABLE IF NOT EXISTS sm_cookies \
   288 				( \
   289 					id INTEGER unsigned NOT NULL auto_increment, \
   290 					name varchar(30) NOT NULL UNIQUE, \
   291 					description varchar(255), \
   292 					access INTEGER, \
   293 					PRIMARY KEY (id) \
   294 				)"))
   295 		{
   296 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
   297 			goto fatal_fail;
   298 		}
   299 
   300 		if (!Database->DoSimpleQuery(
   301 				"CREATE TABLE IF NOT EXISTS sm_cookie_cache \
   302 				( \
   303 					player varchar(65) NOT NULL, \
   304 					cookie_id int(10) NOT NULL, \
   305 					value varchar(100), \
   306 					timestamp int NOT NULL, \
   307 					PRIMARY KEY (player, cookie_id) \
   308 				)"))
   309 		{
   310 			g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
   311 			goto fatal_fail;
   312 		}
   313 	}
   314 	else
   315 	{
   316 		g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
   317 		goto fatal_fail;
   318 	}
   319 
   320 	databaseLoading = false;
   321 
   322 	ProcessQueryCache();
   323 
   324 	return;
   325 
   326 fatal_fail:
   327 	Database->Close();
   328 	Database = NULL;
   329 	databaseLoading = false;
   330 	ProcessQueryCache();
   331 }
   332 
   333 bool ClientPrefs::AddQueryToQueue( TQueryOp *query )
   334 {
   335 	queryMutex->Lock();
   336 
   337 	if (Database == NULL && databaseLoading)
   338 	{
   339 		cachedQueries.push_back(query);
   340 		queryMutex->Unlock();
   341 		return true;
   342 	}
   343 
   344 	queryMutex->Unlock();
   345 
   346 	if (Database)
   347 	{
   348 		query->SetDatabase(Database);
   349 		dbi->AddToThreadQueue(query, PrioQueue_Normal);
   350 		return true;
   351 	}
   352 
   353 	query->Destroy();
   354 
   355 	/* If Database is NULL and we're not in the loading phase it must have failed - Can't do much */
   356 	return false;
   357 }
   358 
   359 void ClientPrefs::ProcessQueryCache()
   360 {
   361 	SourceHook::List<TQueryOp *>::iterator iter;
   362 
   363 	queryMutex->Lock();
   364 
   365 	iter = cachedQueries.begin();
   366 	
   367 	while (iter != cachedQueries.end())
   368 	{
   369 		TQueryOp *op = *iter;
   370 
   371 		if (Database != NULL)
   372 		{
   373 			op->SetDatabase(Database);
   374 			dbi->AddToThreadQueue(op, PrioQueue_Normal);
   375 		}
   376 		else
   377 		{
   378 			op->Destroy();
   379 		}
   380 
   381 		iter++;
   382 	}
   383 
   384 	cachedQueries.clear();
   385 
   386 	queryMutex->Unlock();
   387 }
   388 
   389 size_t IsAuthIdConnected(char *authID)
   390 {
   391 	IGamePlayer *player;
   392 	int maxPlayers = playerhelpers->GetMaxClients();
   393 
   394 	for (int playerIndex = 1; playerIndex <= maxPlayers; playerIndex++)
   395 	{
   396 		player = playerhelpers->GetGamePlayer(playerIndex);
   397 		if (!player || !player->IsConnected())
   398 		{
   399 			continue;
   400 		}
   401 		const char *authString = player->GetAuthString();
   402 		if (!authString || authString[0] == '\0')
   403 		{
   404 			continue;
   405 		}
   406 
   407 		if (strcmp(authString, authID) == 0)
   408 		{
   409 			return playerIndex;
   410 		}
   411 	}
   412 	return 0;
   413 }
   414 
   415 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
   416 {
   417 	va_list ap;
   418 	va_start(ap, fmt);
   419 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
   420 	va_end(ap);
   421 
   422 	if (len >= maxlength)
   423 	{
   424 		buffer[maxlength - 1] = '\0';
   425 		return (maxlength - 1);
   426 	}
   427 	else
   428 	{
   429 		return len;
   430 	}
   431 }
   432 
   433 bool Translate(char *buffer, 
   434 				   size_t maxlength,
   435 				   const char *format,
   436 				   unsigned int numparams,
   437 				   size_t *pOutLength,
   438 				   ...)
   439 {
   440 	va_list ap;
   441 	unsigned int i;
   442 	const char *fail_phrase;
   443 	void *params[MAX_TRANSLATE_PARAMS];
   444 
   445 	if (numparams > MAX_TRANSLATE_PARAMS)
   446 	{
   447 		assert(false);
   448 		return false;
   449 	}
   450 
   451 	va_start(ap, pOutLength);
   452 	for (i = 0; i < numparams; i++)
   453 	{
   454 		params[i] = va_arg(ap, void *);
   455 	}
   456 	va_end(ap);
   457 
   458 	if (!g_ClientPrefs.phrases->FormatString(buffer,
   459 		maxlength, 
   460 		format, 
   461 		params,
   462 		numparams,
   463 		pOutLength,
   464 		&fail_phrase))
   465 	{
   466 		if (fail_phrase != NULL)
   467 		{
   468 			g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase);
   469 		}
   470 		else
   471 		{
   472 			g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase.");
   473 		}
   474 
   475 		return false;
   476 	}
   477 
   478 	return true;
   479 }
   480 
   481 IdentityToken_t *ClientPrefs::GetIdentity() const
   482 {
   483 	return identity;
   484 }
   485 
   486 const char *ClientPrefs::GetExtensionVerString()
   487 {
   488 	return SM_FULL_VERSION;
   489 }
   490 
   491 const char *ClientPrefs::GetExtensionDateString()
   492 {
   493 	return SM_BUILD_TIMESTAMP;
   494 }
   495