pred@2009: /**
pred@2009: * vim: set ts=4 :
pred@2009: * =============================================================================
pred@2009: * SourceMod Client Preferences Extension
pred@2009: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
pred@2009: * =============================================================================
pred@2009: *
pred@2009: * This program is free software; you can redistribute it and/or modify it under
pred@2009: * the terms of the GNU General Public License, version 3.0, as published by the
pred@2009: * Free Software Foundation.
pred@2009: *
pred@2009: * This program is distributed in the hope that it will be useful, but WITHOUT
pred@2009: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
pred@2009: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
pred@2009: * details.
pred@2009: *
pred@2009: * You should have received a copy of the GNU General Public License along with
pred@2009: * this program. If not, see .
pred@2009: *
pred@2009: * As a special exception, AlliedModders LLC gives you permission to link the
pred@2009: * code of this program (as well as its derivative works) to "Half-Life 2," the
pred@2009: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
pred@2009: * by the Valve Corporation. You must obey the GNU General Public License in
pred@2009: * all respects for all other code used. Additionally, AlliedModders LLC grants
pred@2009: * this exception to all derivative works. AlliedModders LLC defines further
pred@2009: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
pred@2009: * or .
pred@2009: *
pred@2009: * Version: $Id$
pred@2009: */
pred@2009:
dvander@2783: #include
pred@2009: #include "extension.h"
pred@2009:
pred@2009: /**
pred@2009: * @file extension.cpp
pred@2009: * @brief Implement extension code here.
pred@2009: */
pred@2009:
pred@2009: ClientPrefs g_ClientPrefs; /**< Global singleton for extension's main interface */
pred@2009:
pred@2009: SMEXT_LINK(&g_ClientPrefs);
pred@2009:
pred@2009: HandleType_t g_CookieType = 0;
pred@2009: CookieTypeHandler g_CookieTypeHandler;
pred@2009:
pred@2048: HandleType_t g_CookieIterator = 0;
pred@2048: CookieIteratorHandler g_CookieIteratorHandler;
dvander@2373: DbDriver g_DriverType;
pred@2009:
pred@2009: bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late)
pred@2207: {
pred@2207: queryMutex = threader->MakeMutex();
pred@2207: cookieMutex = threader->MakeMutex();
pred@2207:
pred@2207: DBInfo = dbi->FindDatabaseConf("clientprefs");
pred@2009:
pred@2009: if (DBInfo == NULL)
pred@2009: {
pred@2009: DBInfo = dbi->FindDatabaseConf("default");
pred@2009:
pred@2009: if (DBInfo == NULL)
pred@2009: {
kyle@3804: DBInfo = dbi->FindDatabaseConf("storage-local");
pred@2009: }
pred@2009: }
kyle@3804:
kyle@3804: if (DBInfo == NULL)
kyle@3804: {
kyle@3804: snprintf(error, maxlength, "Could not find any suitable database configs");
kyle@3804: return false;
kyle@3804: }
pred@2009:
kyle@3804: if (DBInfo->driver && DBInfo->driver[0] != '\0')
pred@2009: {
pred@2009: Driver = dbi->FindOrLoadDriver(DBInfo->driver);
pred@2009: }
pred@2009: else
pred@2009: {
pred@2009: Driver = dbi->GetDefaultDriver();
pred@2009: }
pred@2009:
pred@2009: if (Driver == NULL)
pred@2009: {
pred@2009: snprintf(error, maxlength, "Could not load DB Driver \"%s\"", DBInfo->driver);
pred@2009: return false;
pred@2009: }
pred@2009:
pred@2207: databaseLoading = true;
pred@2207: TQueryOp *op = new TQueryOp(Query_Connect, 0);
pred@2207: dbi->AddToThreadQueue(op, PrioQueue_High);
pred@2009:
dvander@2135: dbi->AddDependency(myself, Driver);
dvander@2135:
pred@2009: sharesys->AddNatives(myself, g_ClientPrefNatives);
pred@2009: sharesys->RegisterLibrary(myself, "clientprefs");
skamonkey@3573: identity = sharesys->CreateIdentity(sharesys->CreateIdentType("ClientPrefs"), this);
pred@2048: g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell);
pred@2009:
pred@2009: g_CookieType = handlesys->CreateType("Cookie",
pred@2009: &g_CookieTypeHandler,
pred@2009: 0,
pred@2009: NULL,
pred@2009: NULL,
pred@2009: myself->GetIdentity(),
pred@2009: NULL);
pred@2009:
pred@2048: g_CookieIterator = handlesys->CreateType("CookieIterator",
pred@2048: &g_CookieIteratorHandler,
pred@2048: 0,
pred@2048: NULL,
pred@2048: NULL,
pred@2048: myself->GetIdentity(),
pred@2048: NULL);
pred@2048:
pred@2048: IMenuStyle *style = menus->GetDefaultStyle();
skamonkey@3573: g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, identity);
pred@2048: g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:");
pred@2048:
pred@2048: plsys->AddPluginsListener(&g_CookieManager);
pred@2048:
pred@2048: phrases = translator->CreatePhraseCollection();
pred@2048: phrases->AddPhraseFile("clientprefs.phrases");
pred@2048: phrases->AddPhraseFile("common.phrases");
pred@2048:
pred@2666: if (late)
pred@2666: {
kyle@3804: this->CatchLateLoadClients();
pred@2666: }
pred@2666:
pred@2009: return true;
pred@2009: }
pred@2009:
pred@2009: void ClientPrefs::SDK_OnAllLoaded()
pred@2009: {
pred@2009: playerhelpers->AddClientListener(&g_CookieManager);
pred@2009: }
pred@2009:
dvander@2135: bool ClientPrefs::QueryInterfaceDrop(SMInterface *pInterface)
dvander@2135: {
dvander@2135: if ((void *)pInterface == (void *)(Database->GetDriver()))
dvander@2135: {
dvander@2135: return false;
dvander@2135: }
dvander@2135:
dvander@2135: return true;
dvander@2135: }
dvander@2135:
dvander@2135: void ClientPrefs::NotifyInterfaceDrop(SMInterface *pInterface)
dvander@2135: {
dvander@2135: if (Database != NULL && (void *)pInterface == (void *)(Database->GetDriver()))
dvander@2135: {
dvander@2135: Database->Close();
dvander@2135: Database = NULL;
dvander@2135: }
dvander@2135: }
dvander@2135:
pred@2009: void ClientPrefs::SDK_OnUnload()
pred@2009: {
pred@2009: handlesys->RemoveType(g_CookieType, myself->GetIdentity());
pred@2131: handlesys->RemoveType(g_CookieIterator, myself->GetIdentity());
pred@2009:
pred@2009: g_CookieManager.Unload();
pred@2009:
dvander@2135: if (Database != NULL)
dvander@2135: {
dvander@2135: Database->Close();
kyle@3804: Database = NULL;
kyle@3804: }
kyle@3804:
kyle@3804: if (g_CookieManager.cookieDataLoadedForward != NULL)
kyle@3804: {
kyle@3804: forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward);
kyle@3804: g_CookieManager.cookieDataLoadedForward = NULL;
dvander@2135: }
pred@2009:
kyle@3804: if (g_CookieManager.clientMenu != NULL)
skamonkey@3573: {
kyle@3804: Handle_t menuHandle = g_CookieManager.clientMenu->GetHandle();
kyle@3804:
kyle@3804: if (menuHandle != BAD_HANDLE)
kyle@3804: {
kyle@3804: HandleSecurity sec = HandleSecurity(identity, identity);
kyle@3804: HandleError err = handlesys->FreeHandle(menuHandle, &sec);
kyle@3804: if (HandleError_None != err)
kyle@3804: {
kyle@3804: g_pSM->LogError(myself, "Error %d when attempting to free client menu handle", err);
kyle@3804: }
kyle@3804: }
kyle@3804:
kyle@3804: g_CookieManager.clientMenu = NULL;
skamonkey@3573: }
pred@2131:
kyle@3804: if (phrases != NULL)
kyle@3804: {
kyle@3804: phrases->Destroy();
kyle@3804: phrases = NULL;
kyle@3804: }
skamonkey@3573:
pred@2131: plsys->RemovePluginsListener(&g_CookieManager);
pred@2131: playerhelpers->RemoveClientListener(&g_CookieManager);
pred@2207:
pred@2207: queryMutex->DestroyThis();
pred@2207: cookieMutex->DestroyThis();
pred@2207: }
pred@2207:
asherkin@3500: void ClientPrefs::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax)
asherkin@3500: {
kyle@3804: this->AttemptReconnection();
kyle@3804: }
kyle@3804:
kyle@3804: void ClientPrefs::AttemptReconnection()
kyle@3804: {
kyle@3804: if (Database || databaseLoading)
asherkin@3500: {
kyle@3804: return; /* We're already loading, or have loaded. */
asherkin@3500: }
kyle@3804:
kyle@3804: g_pSM->LogMessage(myself, "Attempting to reconnect to database...");
kyle@3804: databaseLoading = true;
kyle@3804:
kyle@3804: TQueryOp *op = new TQueryOp(Query_Connect, 0);
kyle@3804: dbi->AddToThreadQueue(op, PrioQueue_High);
kyle@3804:
kyle@3804: this->CatchLateLoadClients(); /* DB reconnection, we should check if we missed anyone... */
asherkin@3500: }
asherkin@3500:
pred@2207: void ClientPrefs::DatabaseConnect()
pred@2207: {
pred@2207: char error[256];
pred@2207: int errCode = 0;
pred@2207:
pred@2207: Database = Driver->Connect(DBInfo, true, error, sizeof(error));
pred@2207:
pred@2207: if (Database == NULL)
pred@2207: {
pred@2207: g_pSM->LogError(myself, error);
pred@2207: databaseLoading = false;
pred@2207: return;
pred@2207: }
pred@2207:
pred@2207: const char *identifier = Driver->GetIdentifier();
pred@2207:
pred@2207: if (strcmp(identifier, "sqlite") == 0)
pred@2207: {
dvander@2373: g_DriverType = Driver_SQLite;
pred@2207:
dvander@2338: if (!Database->DoSimpleQuery(
pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookies \
pred@2207: ( \
pred@2207: id INTEGER PRIMARY KEY AUTOINCREMENT, \
pred@2207: name varchar(30) NOT NULL UNIQUE, \
pred@2207: description varchar(255), \
pred@2207: access INTEGER \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2220:
dvander@2338: if (!Database->DoSimpleQuery(
pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookie_cache \
pred@2207: ( \
pred@2207: player varchar(65) NOT NULL, \
pred@2207: cookie_id int(10) NOT NULL, \
pred@2207: value varchar(100), \
pred@2207: timestamp int, \
pred@2207: PRIMARY KEY (player, cookie_id) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2207: }
pred@2207: else if (strcmp(identifier, "mysql") == 0)
pred@2207: {
dvander@2373: g_DriverType = Driver_MySQL;
pred@2207:
dvander@2338: if (!Database->DoSimpleQuery(
pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookies \
pred@2207: ( \
pred@2207: id INTEGER unsigned NOT NULL auto_increment, \
pred@2207: name varchar(30) NOT NULL UNIQUE, \
pred@2207: description varchar(255), \
pred@2207: access INTEGER, \
pred@2207: PRIMARY KEY (id) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookies: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2220:
dvander@2338: if (!Database->DoSimpleQuery(
pred@2207: "CREATE TABLE IF NOT EXISTS sm_cookie_cache \
pred@2207: ( \
pred@2207: player varchar(65) NOT NULL, \
pred@2207: cookie_id int(10) NOT NULL, \
pred@2207: value varchar(100), \
pred@2207: timestamp int NOT NULL, \
pred@2207: PRIMARY KEY (player, cookie_id) \
dvander@2338: )"))
pred@2220: {
dvander@2338: g_pSM->LogMessage(myself, "Failed to CreateTable sm_cookie_cache: %s", Database->GetError());
dvander@2338: goto fatal_fail;
pred@2220: }
pred@2207: }
pred@2207: else
pred@2207: {
pred@2207: g_pSM->LogError(myself, "Unsupported driver \"%s\"", identifier);
dvander@2338: goto fatal_fail;
pred@2207: }
pred@2207:
pred@2207: databaseLoading = false;
pred@2207:
kyle@3804: this->ProcessQueryCache();
pred@2207: return;
dvander@2338:
dvander@2338: fatal_fail:
dvander@2338: Database->Close();
dvander@2338: Database = NULL;
dvander@2338: databaseLoading = false;
pred@2207: }
pred@2207:
pred@2207: bool ClientPrefs::AddQueryToQueue( TQueryOp *query )
pred@2207: {
pred@2207: queryMutex->Lock();
kyle@3804: if (Database == NULL)
pred@2207: {
pred@2207: cachedQueries.push_back(query);
pred@2207: queryMutex->Unlock();
kyle@3804: return false;
kyle@3804: }
kyle@3804:
kyle@3804: if (!cachedQueries.empty())
kyle@3804: {
kyle@3804: queryMutex->Unlock();
kyle@3804: this->ProcessQueryCache();
kyle@3804: }
kyle@3804: else
kyle@3804: {
kyle@3804: queryMutex->Unlock();
pred@2207: }
pred@2207:
kyle@3804: query->SetDatabase(Database);
kyle@3804: dbi->AddToThreadQueue(query, PrioQueue_Normal);
kyle@3804: return true;
pred@2207: }
pred@2207:
pred@2207: void ClientPrefs::ProcessQueryCache()
pred@2207: {
kyle@3804: if (Database == NULL)
kyle@3804: {
kyle@3804: return;
kyle@3804: }
pred@2207:
pred@2207: queryMutex->Lock();
kyle@3804: TQueryOp *op;
kyle@3804: for (SourceHook::List::iterator iter = cachedQueries.begin(); iter != cachedQueries.end(); iter++)
pred@2207: {
kyle@3804: op = *iter;
kyle@3804: op->SetDatabase(Database);
kyle@3804: dbi->AddToThreadQueue(op, PrioQueue_Normal);
pred@2207: }
pred@2207:
pred@2207: cachedQueries.clear();
pred@2207: queryMutex->Unlock();
pred@2048: }
pred@2048:
datal30mb@3411: size_t IsAuthIdConnected(char *authID)
datal30mb@3411: {
datal30mb@3411: IGamePlayer *player;
kyle@3804: const char *authString;
kyle@3804:
kyle@3804: for (int playerIndex = playerhelpers->GetMaxClients()+1; --playerIndex > 0;)
datal30mb@3411: {
datal30mb@3411: player = playerhelpers->GetGamePlayer(playerIndex);
kyle@3804: if (player == NULL || !player->IsConnected())
datal30mb@3411: {
datal30mb@3411: continue;
datal30mb@3411: }
kyle@3804:
kyle@3804: authString = player->GetAuthString();
kyle@3804:
kyle@3804: if (authString == NULL || authString[0] == '\0')
datal30mb@3411: {
datal30mb@3411: continue;
datal30mb@3411: }
datal30mb@3411:
datal30mb@3411: if (strcmp(authString, authID) == 0)
datal30mb@3411: {
datal30mb@3411: return playerIndex;
datal30mb@3411: }
datal30mb@3411: }
datal30mb@3411: return 0;
datal30mb@3411: }
datal30mb@3411:
kyle@3804: void ClientPrefs::CatchLateLoadClients()
pred@2048: {
kyle@3804: IGamePlayer *pPlayer;
kyle@3804: for (int i = playerhelpers->GetMaxClients()+1; --i > 0;)
kyle@3804: {
kyle@3804: if (g_CookieManager.AreClientCookiesPening(i) || g_CookieManager.AreClientCookiesCached(i))
kyle@3804: {
kyle@3804: continue;
kyle@3804: }
kyle@3804:
kyle@3804: pPlayer = playerhelpers->GetGamePlayer(i);
kyle@3804:
kyle@3804: if (!pPlayer || !pPlayer->IsAuthorized())
kyle@3804: {
kyle@3804: continue;
kyle@3804: }
pred@2048:
kyle@3804: g_CookieManager.OnClientAuthorized(i, pPlayer->GetAuthString());
pred@2048: }
kyle@3804: }
kyle@3804:
kyle@3804: void ClientPrefs::ClearQueryCache(int serial)
kyle@3804: {
kyle@3804: queryMutex->Lock();
kyle@3804:
kyle@3804: for (SourceHook::List::iterator iter = cachedQueries.begin(); iter != cachedQueries.end();)
pred@2048: {
kyle@3804: TQueryOp *op = *iter;
kyle@3804: if (op && op->PullQueryType() == Query_SelectData && op->PullQuerySerial() == serial)
kyle@3804: {
kyle@3804: op->Destroy();
kyle@3804: iter = cachedQueries.erase(iter);
kyle@3804: }
kyle@3804: else
kyle@3804: {
kyle@3804: iter++;
kyle@3804: }
kyle@3804: }
kyle@3804: queryMutex->Unlock();
pred@2048: }
pred@2048:
pred@2048: bool Translate(char *buffer,
pred@2048: size_t maxlength,
pred@2048: const char *format,
pred@2048: unsigned int numparams,
pred@2048: size_t *pOutLength,
pred@2048: ...)
pred@2048: {
pred@2048: va_list ap;
pred@2048: unsigned int i;
pred@2048: const char *fail_phrase;
pred@2048: void *params[MAX_TRANSLATE_PARAMS];
pred@2048:
pred@2048: if (numparams > MAX_TRANSLATE_PARAMS)
pred@2048: {
pred@2048: assert(false);
pred@2048: return false;
pred@2048: }
pred@2048:
pred@2048: va_start(ap, pOutLength);
pred@2048: for (i = 0; i < numparams; i++)
pred@2048: {
pred@2048: params[i] = va_arg(ap, void *);
pred@2048: }
pred@2048: va_end(ap);
pred@2048:
pred@2048: if (!g_ClientPrefs.phrases->FormatString(buffer,
pred@2048: maxlength,
pred@2048: format,
pred@2048: params,
pred@2048: numparams,
pred@2048: pOutLength,
pred@2048: &fail_phrase))
pred@2048: {
pred@2048: if (fail_phrase != NULL)
pred@2048: {
pred@2048: g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase);
pred@2048: }
pred@2048: else
pred@2048: {
pred@2048: g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase.");
pred@2048: }
pred@2048:
pred@2048: return false;
pred@2048: }
pred@2048:
pred@2048: return true;
pred@2009: }
pred@2009:
kyle@3804: char * UTIL_strncpy(char * destination, const char * source, size_t num)
kyle@3804: {
kyle@3804: if (source == NULL)
kyle@3804: {
kyle@3804: destination[0] = '\0';
kyle@3804: return destination;
kyle@3804: }
kyle@3804:
kyle@3804: size_t req = strlen(source);
kyle@3804: if (!req)
kyle@3804: {
kyle@3804: destination[0] = '\0';
kyle@3804: return destination;
kyle@3804: }
kyle@3804: else if (req >= num)
kyle@3804: {
kyle@3804: req = num-1;
kyle@3804: }
kyle@3804:
kyle@3804: strncpy(destination, source, req);
kyle@3804: destination[req] = '\0';
kyle@3804: return destination;
kyle@3804: }
kyle@3804:
skamonkey@3573: IdentityToken_t *ClientPrefs::GetIdentity() const
skamonkey@3573: {
skamonkey@3573: return identity;
skamonkey@3573: }
skamonkey@3573:
dvander@2790: const char *ClientPrefs::GetExtensionVerString()
dvander@2783: {
asherkin@3630: return SM_VERSION_STRING;
dvander@2783: }
pred@2009:
dvander@2790: const char *ClientPrefs::GetExtensionDateString()
dvander@2783: {
dvander@2783: return SM_BUILD_TIMESTAMP;
dvander@2783: }
pred@2048:
kyle@3804: ClientPrefs::ClientPrefs()
kyle@3804: {
kyle@3804: Driver = NULL;
kyle@3804: Database = NULL;
kyle@3804: databaseLoading = false;
kyle@3804: phrases = NULL;
kyle@3804: DBInfo = NULL;
kyle@3804:
kyle@3804: cookieMutex = NULL;
kyle@3804: queryMutex = NULL;
kyle@3804: identity = NULL;
kyle@3804: }