/* EQ2Emulator: Everquest II Server Emulator Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) This file is part of EQ2Emulator. EQ2Emulator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. EQ2Emulator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ #include "../common/debug.h" #include using namespace std; #include #include #include #include //#include #include #include #include #include // Disgrace: for windows compile #ifdef WIN32 #include #include #define snprintf _snprintf #define strncasecmp _strnicmp #define strcasecmp _stricmp #else #include "unix.h" #include #endif #include "database.h" #include "EQStream.h" #include "packet_functions.h" #include "emu_opcodes.h" #ifdef WORLD #include "../WorldServer/WorldDatabase.h" extern WorldDatabase database; #endif #ifdef LOGIN #include "../LoginServer/LoginDatabase.h" extern LoginDatabase database; #endif #ifdef PARSER #include "../PacketParser/ParserDatabase.h" extern ParserDatabase database; #endif #ifdef PATCHER #include "../PatchServer/PatcherDatabase.h" extern PatcherDatabase database; #endif #include "../common/EQEMuError.h" #include "../common/packet_dump.h" #include "../common/Log.h" #ifdef WORLD ThreadReturnType DBAsyncQueries(void* str) { // allow some buffer for multiple queries to collect Sleep(10); DBStruct* data = (DBStruct*)str; database.RunAsyncQueries(data->queryid); delete data; THREAD_RETURN(NULL); } #endif Database::Database() { InitVars(); } bool Database::Init(bool silentLoad) { char host[200], user[200], passwd[200], database[200]; unsigned int port=0; bool compression = false; bool items[6] = {false, false, false, false, false, false}; const char* exampleIni[] = { "[Database]", "host = localhost", "user = root", "password = pass", "database = dbname", "### --- Assure each parameter is on a new line!" }; if(!ReadDBINI(host, user, passwd, database, &port, &compression, items)) { //exit(1); return false; } if (!items[0] || !items[1] || !items[2] || !items[3]) { LogWrite(DATABASE__ERROR, 0, "DB", "Database file %s is incomplete.", DB_INI_FILE); int i; for (i = 0; i < 4; i++) { if ( !items[i] ) LogWrite(DATABASE__ERROR, 0, "DB", "Could not find parameter %s", exampleIni[i+1]); // offset by 1 because the [Database] entry } LogWrite(DATABASE__ERROR, 0, "DB", "Example File:"); int length = sizeof exampleIni / sizeof exampleIni[0]; for(i=0;i Database::GetVersions(){ map opcodes; Query query; MYSQL_ROW row; MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select distinct version_range1, version_range2 from opcodes"); while(result && (row = mysql_fetch_row(result))){ if(row[0] && row[1]) opcodes[atoi(row[0])] = atoi(row[1]); } return opcodes; } map Database::GetOpcodes(int16 version){ map opcodes; Query query; MYSQL_ROW row; MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select name, opcode from opcodes where %i between version_range1 and version_range2 order by version_range1, id", version); while(result && (row = mysql_fetch_row(result))){ opcodes[row[0]] = atoi(row[1]); } return opcodes; } int32 Database::AuthenticateWebUser(char* userName, char* passwd, int32* status){ if(status) { *status = 0; } Query query; MYSQL_ROW row; int32 id = 0; MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select id, status from web_users where username='%s' and passwd = sha2('%s', 512)", getSafeEscapeString(userName).c_str(), getSafeEscapeString(passwd).c_str()); if(result && (row = mysql_fetch_row(result))){ id = atoul(row[0]); if(status) { *status = atoul(row[1]); } } return id; } int32 Database::NoAuthRoute(char* route){ Query query; MYSQL_ROW row; int32 status = 0xFFFFFFFF; MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select status from web_routes where route='%s'", getSafeEscapeString(route).c_str()); if(result && (row = mysql_fetch_row(result))){ status = atoul(row[0]); } return status; } void Database::HandleMysqlError(int32 errnum) { switch(errnum) { case 0: break; case 1045: // Access Denied case 2001: { AddEQEMuError(EQEMuError_Mysql_1405, true); break; } case 2003: { // Unable to connect AddEQEMuError(EQEMuError_Mysql_2003, true); break; } case 2005: { // Unable to connect AddEQEMuError(EQEMuError_Mysql_2005, true); break; } case 2007: { // Unable to connect AddEQEMuError(EQEMuError_Mysql_2007, true); break; } } } void Database::InitVars() { } Database::~Database() { #ifdef WORLD DBQueryMutex.writelock(__FUNCTION__, __LINE__); activeQuerySessions.clear(); DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); DBAsyncMutex.writelock(); continueAsync = false; map>::iterator itr; for (itr = asyncQueries.begin(); itr != asyncQueries.end(); itr++) { asyncQueriesMutex[itr->first]->writelock(); deque queries = itr->second; while (queries.size() > 0) { Query* cur = queries.front(); queries.pop_front(); safe_delete(cur); } asyncQueriesMutex[itr->first]->releasewritelock(); Mutex* mutex = asyncQueriesMutex[itr->first]; asyncQueriesMutex.erase(itr->first); safe_delete(mutex); } asyncQueries.clear(); asyncQueriesMutex.clear(); DBAsyncMutex.releasewritelock(); PurgeDBInstances(); #endif } #ifdef WORLD void Query::AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...) { in_type = type; va_list args; va_start(args, format); #ifdef WIN32 char* buffer; int buf_len = _vscprintf(format, args) + 1; buffer = new char[buf_len]; vsprintf(buffer, format, args); #else char* buffer; int buf_len; va_list argcopy; va_copy(argcopy, args); buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; va_end(argcopy); buffer = new char[buf_len]; vsnprintf(buffer, buf_len, format, args); #endif va_end(args); query = string(buffer); Query* asyncQuery = new Query(this, queryID); safe_delete_array(buffer); db->AddAsyncQuery(asyncQuery); } void Query::RunQueryAsync(Database* db) { db->RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); } #endif MYSQL_RES* Query::RunQuery2(QUERY_TYPE type, const char* format, ...){ va_list args; va_start( args, format ); #ifdef WIN32 char * buffer; int buf_len = _vscprintf( format, args ) + 1; buffer = new char[buf_len]; vsprintf( buffer, format, args ); #else char* buffer; int buf_len; va_list argcopy; va_copy(argcopy, args); buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; va_end(argcopy); buffer = new char[buf_len]; vsnprintf(buffer, buf_len, format, args); #endif va_end(args); query = string(buffer); safe_delete_array( buffer ); return RunQuery2(query.c_str(), type); } MYSQL_RES* Query::RunQuery2(string in_query, QUERY_TYPE type){ switch(type){ case Q_SELECT: break; case Q_DBMS: case Q_REPLACE: case Q_DELETE: case Q_UPDATE: safe_delete(affected_rows); affected_rows = new int32; break; case Q_INSERT: safe_delete(last_insert_id); last_insert_id = new int32; } if(result){ if(!multiple_results) multiple_results = new vector(); multiple_results->push_back(result); } query = in_query; #if defined WORLD && defined _DEBUG if (type == Q_UPDATE || type == Q_INSERT || type == Q_DELETE || type == Q_REPLACE) { char* filteredTables[] = { " characters", " character_", " `character_", " statistics", " variables", " char_colors", " `guild", " bugs" }; bool match = false; for (int i = 0; i < sizeof(filteredTables) / sizeof(filteredTables[0]); i++) { if (query.find(filteredTables[i]) != std::string::npos) { match = true; break; } } try { if (!match) { FILE* pFile; pFile = fopen("sql_updates.sql", "a+"); fwrite(query.c_str(), 1, query.length(), pFile); fwrite(";", sizeof(char), 1, pFile); fwrite("\n", sizeof(char), 1, pFile); fclose(pFile); } } catch (...) {} } #endif database.RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); return result; } #ifdef WORLD void Database::RunAsyncQueries(int32 queryid) { Database* asyncdb = FindFreeInstance(); DBAsyncMutex.writelock(); map>::iterator itr = asyncQueries.find(queryid); if (itr == asyncQueries.end()) { DBAsyncMutex.releasewritelock(); return; } asyncQueriesMutex[queryid]->writelock(); deque queries; while (itr->second.size()) { Query* cur = itr->second.front(); queries.push_back(cur); itr->second.pop_front(); } itr->second.clear(); asyncQueries.erase(itr); DBAsyncMutex.releasewritelock(); asyncQueriesMutex[queryid]->releasewritelock(); int32 count = 0; while (queries.size() > 0) { Query* cur = queries.front(); cur->RunQueryAsync(asyncdb); this->RemoveActiveQuery(cur); queries.pop_front(); safe_delete(cur); } FreeDBInstance(asyncdb); bool isActive = IsActiveQuery(queryid); if (isActive) { continueAsync = true; DBStruct* tmp = new DBStruct; tmp->queryid = queryid; #ifdef WIN32 _beginthread(DBAsyncQueries, 0, (void*)tmp); #else pthread_t t1; pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); pthread_detach(t1); #endif } } void Database::AddAsyncQuery(Query* query) { DBAsyncMutex.writelock(); map::iterator mutexItr = asyncQueriesMutex.find(query->GetQueryID()); if (mutexItr == asyncQueriesMutex.end()) { Mutex* queryMutex = new Mutex(); queryMutex->SetName("AsyncQuery" + query->GetQueryID()); asyncQueriesMutex.insert(make_pair(query->GetQueryID(), queryMutex)); } map>::iterator itr = asyncQueries.find(query->GetQueryID()); asyncQueriesMutex[query->GetQueryID()]->writelock(); if ( itr != asyncQueries.end()) itr->second.push_back(query); else { deque queue; queue.push_back(query); asyncQueries.insert(make_pair(query->GetQueryID(), queue)); } AddActiveQuery(query); asyncQueriesMutex[query->GetQueryID()]->releasewritelock(); DBAsyncMutex.releasewritelock(); bool isActive = IsActiveQuery(query->GetQueryID(), query); if (!isActive) { continueAsync = true; DBStruct* tmp = new DBStruct; tmp->queryid = query->GetQueryID(); #ifdef WIN32 _beginthread(DBAsyncQueries, 0, (void*)tmp); #else pthread_t t1; pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); pthread_detach(t1); #endif } } Database* Database::FindFreeInstance() { Database* db_inst = 0; map::iterator itr; DBInstanceMutex.writelock(__FUNCTION__, __LINE__); for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { if (!itr->second) { db_inst = itr->first; itr->second = true; break; } } if (!db_inst) { WorldDatabase* tmp = new WorldDatabase(); db_inst = (Database*)tmp; tmp->Init(); tmp->ConnectNewDatabase(); dbInstances.insert(make_pair(db_inst, true)); } DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); return db_inst; } void Database::PurgeDBInstances() { map::iterator itr; DBInstanceMutex.writelock(__FUNCTION__, __LINE__); for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { WorldDatabase* tmpInst = (WorldDatabase*)itr->first; safe_delete(tmpInst); } dbInstances.clear(); DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); } void Database::PingAsyncDatabase() { map::iterator itr; DBInstanceMutex.readlock(__FUNCTION__, __LINE__); for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { Database* tmpInst = itr->first; tmpInst->ping(); } DBInstanceMutex.releasereadlock(__FUNCTION__, __LINE__); } void Database::FreeDBInstance(Database* cur) { DBInstanceMutex.writelock(__FUNCTION__, __LINE__); dbInstances[cur] = false; DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); } void Database::RemoveActiveQuery(Query* query) { DBQueryMutex.writelock(__FUNCTION__, __LINE__); vector::iterator itr; for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) { Query* curQuery = *itr; if (query == curQuery) { activeQuerySessions.erase(itr); break; } } DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); } void Database::AddActiveQuery(Query* query) { DBQueryMutex.writelock(__FUNCTION__, __LINE__); activeQuerySessions.push_back(query); DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); } bool Database::IsActiveQuery(int32 id, Query* skip) { bool isActive = false; DBQueryMutex.readlock(__FUNCTION__, __LINE__); vector::iterator itr; for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) { Query* query = *itr; if (query == skip) continue; if (query->GetQueryID() == id) { isActive = true; break; } } DBQueryMutex.releasereadlock(__FUNCTION__, __LINE__); return isActive; } #endif