2020-02-28 07:17:24 -07:00
/*
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 < http : //www.gnu.org/licenses/>.
*/
# include "../common/debug.h"
# include <iostream>
using namespace std ;
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errmsg.h>
//#include <mysqld_error.h>
# include <limits.h>
# include <ctype.h>
# include <assert.h>
# include <map>
// Disgrace: for windows compile
# ifdef WIN32
# include <WinSock2.h>
# include <windows.h>
# define snprintf _snprintf
# define strncasecmp _strnicmp
# define strcasecmp _stricmp
# else
# include "unix.h"
# include <netinet/in.h>
# 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"
2020-03-25 21:15:31 -06:00
# ifdef WORLD
ThreadReturnType DBAsyncQueries ( void * str )
{
// allow some buffer for multiple queries to collect
Sleep ( 10 ) ;
DBStruct * data = ( DBStruct * ) str ;
database . RunAsyncQueries ( data - > queryid ) ;
2020-03-26 15:43:01 -06:00
delete data ;
2020-03-25 21:15:31 -06:00
THREAD_RETURN ( NULL ) ;
}
# endif
2020-02-28 07:17:24 -07:00
Database : : Database ( )
{
InitVars ( ) ;
}
2020-03-25 21:15:31 -06:00
bool Database : : Init ( bool silentLoad ) {
2020-02-28 07:17:24 -07:00
char host [ 200 ] , user [ 200 ] , passwd [ 200 ] , database [ 200 ] ;
int32 port = 0 ;
bool compression = false ;
bool items [ 6 ] = { false , false , false , false , false , false } ;
2020-03-28 10:56:59 -06:00
const char * exampleIni [ ] = { " [Database] " , " host = localhost " , " user = root " , " password = pass " , " database = dbname " , " ### --- Assure each parameter is on a new line! " } ;
2020-02-28 07:17:24 -07:00
if ( ! ReadDBINI ( host , user , passwd , database , port , compression , items ) ) {
//exit(1);
return false ;
}
if ( ! items [ 0 ] | | ! items [ 1 ] | | ! items [ 2 ] | | ! items [ 3 ] )
{
2020-03-28 10:56:59 -06:00
LogWrite ( DATABASE__ERROR , 0 , " DB " , " Database file %s is incomplete. " , DB_INI_FILE ) ;
int i ;
for ( i = 0 ; i < 4 ; i + + )
{
if ( ! items [ i ] )
2020-06-06 05:58:17 -06:00
LogWrite ( DATABASE__ERROR , 0 , " DB " , " Could not find parameter %s " , exampleIni [ i + 1 ] ) ; // offset by 1 because the [Database] entry
2020-03-28 10:56:59 -06:00
}
LogWrite ( DATABASE__ERROR , 0 , " DB " , " Example File: " ) ;
int length = sizeof exampleIni / sizeof exampleIni [ 0 ] ;
for ( i = 0 ; i < length ; i + + )
LogWrite ( DATABASE__ERROR , 0 , " DB " , " %s " , exampleIni [ i ] ) ;
2020-02-28 07:17:24 -07:00
//exit (1);
return false ;
}
int32 errnum = 0 ;
char errbuf [ MYSQL_ERRMSG_SIZE ] ;
if ( ! Open ( host , user , passwd , database , port , & errnum , errbuf ) )
{
LogWrite ( DATABASE__ERROR , 0 , " DB " , " Failed to connect to database: Error: %s " , errbuf ) ;
HandleMysqlError ( errnum ) ;
//exit(1);
return false ;
}
else
{
2020-03-25 21:15:31 -06:00
if ( ! silentLoad )
LogWrite ( DATABASE__INFO , 0 , " DB " , " Using database '%s' at %s " , database , host ) ;
2020-02-28 07:17:24 -07:00
}
return true ;
}
map < int16 , int16 > Database : : GetVersions ( ) {
map < int16 , int16 > 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 < string , uint16 > Database : : GetOpcodes ( int16 version ) {
map < string , uint16 > 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 ;
}
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 ( )
{
2020-03-25 21:15:31 -06:00
# ifdef WORLD
DBQueryMutex . writelock ( __FUNCTION__ , __LINE__ ) ;
activeQuerySessions . clear ( ) ;
DBQueryMutex . releasewritelock ( __FUNCTION__ , __LINE__ ) ;
DBAsyncMutex . writelock ( ) ;
continueAsync = false ;
map < int32 , deque < Query * > > : : iterator itr ;
for ( itr = asyncQueries . begin ( ) ; itr ! = asyncQueries . end ( ) ; itr + + )
{
asyncQueriesMutex [ itr - > first ] - > writelock ( ) ;
deque < Query * > 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 ) ;
2020-02-28 07:17:24 -07:00
}
2020-03-25 21:15:31 -06:00
# endif
2020-02-28 07:17:24 -07:00
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 < MYSQL_RES * > ( ) ;
multiple_results - > push_back ( result ) ;
}
query = in_query ;
2020-03-06 14:10:08 -07:00
2020-03-06 16:30:25 -07:00
# if defined WORLD && defined _DEBUG
2020-03-06 14:10:08 -07:00
if ( type = = Q_UPDATE | | type = = Q_INSERT | | type = = Q_DELETE | | type = = Q_REPLACE )
{
2020-03-21 05:42:40 -06:00
char * filteredTables [ ] = { " characters " , " character_ " , " `character_ " , " statistics " , " variables " , " char_colors " , " `guild " , " bugs " } ;
2020-03-06 14:10:08 -07:00
bool match = false ;
for ( int i = 0 ; i < sizeof ( filteredTables ) / sizeof ( filteredTables [ 0 ] ) ; i + + )
{
if ( query . find ( filteredTables [ i ] ) ! = std : : string : : npos ) {
match = true ;
2020-03-30 17:22:47 -06:00
break ;
2020-03-06 14:10:08 -07:00
}
}
try
{
if ( ! match )
{
FILE * pFile ;
pFile = fopen ( " sql_updates.sql " , " a+ " ) ;
fwrite ( query . c_str ( ) , 1 , query . length ( ) , pFile ) ;
2020-03-10 09:19:17 -06:00
fwrite ( " ; " , sizeof ( char ) , 1 , pFile ) ;
2020-03-06 14:10:08 -07:00
fwrite ( " \n " , sizeof ( char ) , 1 , pFile ) ;
fclose ( pFile ) ;
}
}
catch ( . . . ) { }
}
# endif
2020-02-28 07:17:24 -07:00
database . RunQuery ( query . c_str ( ) , query . length ( ) , errbuf , & result , affected_rows , last_insert_id , & errnum , retry ) ;
return result ;
}
2020-03-25 21:15:31 -06:00
# ifdef WORLD
void Database : : RunAsyncQueries ( int32 queryid )
{
Database * asyncdb = FindFreeInstance ( ) ;
DBAsyncMutex . writelock ( ) ;
map < int32 , deque < Query * > > : : iterator itr = asyncQueries . find ( queryid ) ;
if ( itr = = asyncQueries . end ( ) )
{
DBAsyncMutex . releasewritelock ( ) ;
return ;
}
asyncQueriesMutex [ queryid ] - > writelock ( ) ;
deque < Query * > 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 ( ) ;
2020-03-29 08:07:50 -06:00
asyncQueriesMutex [ queryid ] - > releasewritelock ( ) ;
2020-03-25 21:15:31 -06:00
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 ) ;
2020-03-29 08:07:50 -06:00
bool isActive = IsActiveQuery ( queryid ) ;
if ( isActive )
{
continueAsync = true ;
DBStruct * tmp = new DBStruct ;
tmp - > queryid = queryid ;
# ifdef WIN32
_beginthread ( DBAsyncQueries , 0 , ( void * ) tmp ) ;
# else
2020-03-29 17:53:25 -06:00
pthread_t t1 ;
2020-03-29 08:07:50 -06:00
pthread_create ( & t1 , NULL , DBAsyncQueries , ( void * ) tmp ) ;
pthread_detach ( t1 ) ;
# endif
}
2020-03-25 21:15:31 -06:00
}
void Database : : AddAsyncQuery ( Query * query )
{
DBAsyncMutex . writelock ( ) ;
map < int32 , Mutex * > : : 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 < int32 , deque < Query * > > : : iterator itr = asyncQueries . find ( query - > GetQueryID ( ) ) ;
asyncQueriesMutex [ query - > GetQueryID ( ) ] - > writelock ( ) ;
if ( itr ! = asyncQueries . end ( ) )
itr - > second . push_back ( query ) ;
else
{
deque < Query * > 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
2020-03-29 17:53:25 -06:00
pthread_t t1 ;
2020-03-25 21:15:31 -06:00
pthread_create ( & t1 , NULL , DBAsyncQueries , ( void * ) tmp ) ;
pthread_detach ( t1 ) ;
# endif
}
}
Database * Database : : FindFreeInstance ( )
{
Database * db_inst = 0 ;
map < Database * , bool > : : 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 < Database * , bool > : : iterator itr ;
DBInstanceMutex . writelock ( __FUNCTION__ , __LINE__ ) ;
for ( itr = dbInstances . begin ( ) ; itr ! = dbInstances . end ( ) ; itr + + ) {
Database * tmpInst = itr - > first ;
safe_delete ( tmpInst ) ;
}
dbInstances . clear ( ) ;
DBInstanceMutex . releasewritelock ( __FUNCTION__ , __LINE__ ) ;
}
2020-04-23 13:09:39 -06:00
void Database : : PingAsyncDatabase ( )
{
map < Database * , bool > : : iterator itr ;
DBInstanceMutex . readlock ( __FUNCTION__ , __LINE__ ) ;
for ( itr = dbInstances . begin ( ) ; itr ! = dbInstances . end ( ) ; itr + + ) {
Database * tmpInst = itr - > first ;
tmpInst - > ping ( ) ;
}
DBInstanceMutex . releasereadlock ( __FUNCTION__ , __LINE__ ) ;
}
2020-03-25 21:15:31 -06:00
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 < Query * > : : 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 < Query * > : : 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