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: }