2023-10-10 00:40:48 +00:00
# include "MySQL.h"
# pragma warning (disable:4251)
# include "Game.h"
# include "dConfig.h"
# include "dLogger.h"
MySQLDatabase : : MySQLDatabase ( const std : : string & host , const std : : string & database , const std : : string & username , const std : : string & password ) {
this - > m_Host = host ;
this - > m_Database = database ;
this - > m_Username = username ;
this - > m_Password = password ;
m_Driver = sql : : mariadb : : get_driver_instance ( ) ;
sql : : Properties properties ;
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
// 1) it tries to parse a database from the connection string (like in tcp://localhost:3001/darkflame) based on the
// presence of a /
// 2) even avoiding that, the connector still assumes you're connecting with a tcp socket
// So, what we do in the presence of a unix socket or pipe is to set the hostname to the protocol and localhost,
// which avoids parsing errors while still ensuring the correct connection type is used, and then setting the appropriate
// property manually (which the URL parsing fails to do)
const std : : string UNIX_PROTO = " unix:// " ;
const std : : string PIPE_PROTO = " pipe:// " ;
if ( this - > m_Host . find ( UNIX_PROTO ) = = 0 ) {
properties [ " hostName " ] = " unix://localhost " ;
properties [ " localSocket " ] = this - > m_Host . substr ( UNIX_PROTO . length ( ) ) . c_str ( ) ;
} else if ( this - > m_Host . find ( PIPE_PROTO ) = = 0 ) {
properties [ " hostName " ] = " pipe://localhost " ;
properties [ " pipe " ] = this - > m_Host . substr ( PIPE_PROTO . length ( ) ) . c_str ( ) ;
} else {
properties [ " hostName " ] = this - > m_Host . c_str ( ) ;
}
properties [ " user " ] = this - > m_Username . c_str ( ) ;
properties [ " password " ] = this - > m_Password . c_str ( ) ;
properties [ " autoReconnect " ] = " true " ;
this - > m_Properties = properties ;
this - > m_Database = database ;
}
MySQLDatabase : : ~ MySQLDatabase ( ) {
this - > Destroy ( ) ;
}
void MySQLDatabase : : Connect ( ) {
2023-10-10 09:22:31 +00:00
if ( this - > m_Properties . find ( " localSocket " ) ! = this - > m_Properties . end ( ) | | this - > m_Properties . find ( " pipe " ) ! = this - > m_Properties . end ( ) ) {
2023-10-10 00:40:48 +00:00
this - > m_Connection = m_Driver - > connect ( this - > m_Properties ) ;
} else {
this - > m_Connection = m_Driver - > connect (
this - > m_Properties [ " hostName " ] . c_str ( ) ,
this - > m_Properties [ " user " ] . c_str ( ) ,
this - > m_Properties [ " password " ] . c_str ( )
) ;
}
this - > m_Connection - > setSchema ( this - > m_Database . c_str ( ) ) ;
}
void MySQLDatabase : : Destroy ( ) {
if ( this - > m_Connection ! = nullptr ) {
this - > m_Connection - > close ( ) ;
delete this - > m_Connection ;
this - > m_Connection = nullptr ;
}
}
sql : : Statement * MySQLDatabase : : CreateStmt ( ) {
2023-10-10 09:22:31 +00:00
sql : : Statement * toReturn = this - > m_Connection - > createStatement ( ) ;
2023-10-10 00:40:48 +00:00
return toReturn ;
}
sql : : PreparedStatement * MySQLDatabase : : CreatePreppedStmt ( const std : : string & query ) {
const char * test = query . c_str ( ) ;
size_t size = query . length ( ) ;
sql : : SQLString str ( test , size ) ;
if ( ! this - > m_Connection ) {
Connect ( ) ;
Game : : logger - > Log ( " Database " , " Trying to reconnect to MySQL " ) ;
}
2023-10-10 09:22:31 +00:00
if ( ! this - > m_Connection - > isValid ( ) | | this - > m_Connection - > isClosed ( ) ) {
2023-10-10 00:40:48 +00:00
delete this - > m_Connection ;
this - > m_Connection = nullptr ;
Connect ( ) ;
Game : : logger - > Log ( " Database " , " Trying to reconnect to MySQL from invalid or closed connection " ) ;
}
auto * stmt = this - > m_Connection - > prepareStatement ( str ) ;
return stmt ;
}
std : : unique_ptr < sql : : PreparedStatement > MySQLDatabase : : CreatePreppedStmtUnique ( const std : : string & query ) {
2023-10-10 09:22:31 +00:00
std : : unique_ptr < sql : : PreparedStatement > stmt ( CreatePreppedStmt ( query ) ) ;
return stmt ;
2023-10-10 00:40:48 +00:00
}
std : : unique_ptr < sql : : ResultSet > MySQLDatabase : : GetResultsOfStatement ( sql : : Statement * stmt ) {
std : : unique_ptr < sql : : ResultSet > result ( stmt - > executeQuery ( ) ) ;
return result ;
}
void MySQLDatabase : : Commit ( ) {
2023-10-10 09:22:31 +00:00
this - > m_Connection - > commit ( ) ;
2023-10-10 00:40:48 +00:00
}
bool MySQLDatabase : : GetAutoCommit ( ) {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
return m_Connection - > getAutoCommit ( ) ;
}
void MySQLDatabase : : SetAutoCommit ( bool value ) {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
m_Connection - > setAutoCommit ( value ) ;
}
SocketDescriptor MySQLDatabase : : GetMasterServerIP ( ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT ip, port FROM servers WHERE name = 'master'; " ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
return SocketDescriptor ( res - > getInt ( " port " ) , res - > getString ( " ip " ) ) ;
}
return SocketDescriptor ( 0 , " " ) ;
}
void MySQLDatabase : : CreateServer ( const std : : string & name , const std : : string & ip , uint16_t port , uint32_t state , uint32_t version ) {
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO servers (name, ip, port, state, version) VALUES (?, ?, ?, ?, ?); " ) ;
stmt - > setString ( 1 , name ) ;
stmt - > setString ( 2 , ip ) ;
stmt - > setInt ( 3 , port ) ;
stmt - > setInt ( 4 , state ) ;
stmt - > setInt ( 5 , version ) ;
stmt - > execute ( ) ;
}
void MySQLDatabase : : SetServerIpAndPortByName ( const std : : string & name , const std : : string & ip , uint16_t port ) {
auto stmt = CreatePreppedStmtUnique ( " UPDATE servers SET ip = ?, port = ? WHERE name = ?; " ) ;
stmt - > setString ( 1 , ip ) ;
stmt - > setInt ( 2 , port ) ;
stmt - > setString ( 3 , name ) ;
stmt - > execute ( ) ;
}
std : : vector < std : : string > MySQLDatabase : : GetAllCharacterNames ( ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT name FROM charinfo; " ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
std : : vector < std : : string > names ;
while ( res - > next ( ) ) {
2023-10-10 09:22:31 +00:00
names . push_back ( res - > getString ( " name " ) . c_str ( ) ) ;
2023-10-10 00:40:48 +00:00
}
return names ;
}
bool MySQLDatabase : : IsCharacterNameAvailable ( const std : : string & name ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT name FROM charinfo WHERE name = ? OR pending_name = ?; " ) ;
stmt - > setString ( 1 , name ) ;
stmt - > setString ( 2 , name ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
return ! res - > next ( ) ;
}
void MySQLDatabase : : InsertIntoActivityLog ( uint32_t playerId , uint32_t activityId , uint32_t timestamp , uint32_t zoneId ) {
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?); " ) ;
stmt - > setUInt ( 1 , playerId ) ;
stmt - > setUInt ( 2 , activityId ) ;
stmt - > setUInt ( 3 , timestamp ) ;
stmt - > setUInt ( 4 , zoneId ) ;
stmt - > executeUpdate ( ) ;
}
void MySQLDatabase : : InsertIntoCommandLog ( uint32_t playerId , const std : : string & command ) {
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO command_log (character_id, command) VALUES (?, ?); " ) ;
stmt - > setUInt ( 1 , playerId ) ;
stmt - > setString ( 2 , command ) ;
stmt - > executeUpdate ( ) ;
}
CharacterInfo MySQLDatabase : : GetCharacterInfoByID ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT account_id, name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id = ? LIMIT 1; " ) ;
stmt - > setUInt ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
CharacterInfo info { } ;
info . AccountID = res - > getUInt ( " account_id " ) ;
info . ID = id ;
info . Name = res - > getString ( " name " ) ;
info . PendingName = res - > getString ( " pending_name " ) ;
info . NameRejected = res - > getBoolean ( " needs_rename " ) ;
info . PropertyCloneID = res - > getUInt ( " prop_clone_id " ) ;
2023-10-10 09:22:31 +00:00
info . PermissionMap = ( ePermissionMap ) res - > getUInt ( " permission_map " ) ;
2023-10-10 00:40:48 +00:00
return info ;
}
return CharacterInfo { } ;
}
CharacterInfo MySQLDatabase : : GetCharacterInfoByName ( const std : : string & name ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT id FROM charinfo WHERE name = ? " ) ;
stmt - > setString ( 1 , name ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
2023-10-10 09:22:31 +00:00
return GetCharacterInfoByID ( res - > getUInt ( " id " ) ) ;
2023-10-10 00:40:48 +00:00
}
return CharacterInfo { } ;
}
uint32_t MySQLDatabase : : GetLatestCharacterOfAccount ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 1; " ) ;
stmt - > setUInt ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
return res - > getUInt ( " id " ) ;
}
return 0 ;
}
std : : string MySQLDatabase : : GetCharacterXMLByID ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT xml_data FROM charxml WHERE id = ? LIMIT 1; " ) ;
stmt - > setUInt ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
2023-10-10 09:22:31 +00:00
return res - > getString ( " xml_data " ) . c_str ( ) ;
2023-10-10 00:40:48 +00:00
}
return " " ;
}
void MySQLDatabase : : CreateCharacterXML ( uint32_t id , const std : : string & xml ) {
2023-10-10 09:22:31 +00:00
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO charxml (id, xml_data) VALUES (?, ?); " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > setString ( 2 , xml ) ;
2023-10-10 00:40:48 +00:00
stmt - > executeUpdate ( ) ;
}
void MySQLDatabase : : UpdateCharacterXML ( uint32_t id , const std : : string & xml ) {
2023-10-10 09:22:31 +00:00
auto stmt = CreatePreppedStmtUnique ( " UPDATE charxml SET xml_data = ? WHERE id = ?; " ) ;
stmt - > setString ( 1 , xml ) ;
stmt - > setUInt ( 2 , id ) ;
2023-10-10 00:40:48 +00:00
stmt - > executeUpdate ( ) ;
}
void MySQLDatabase : : CreateCharacter ( uint32_t id , uint32_t account_id , const std : : string & name , const std : : string & pending_name , bool needs_rename , uint64_t last_login ) {
2023-10-10 09:22:31 +00:00
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO charinfo (id, account_id, name, pending_name, needs_rename, last_login) VALUES (?, ?, ?, ?, ?, ?); " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > setUInt ( 2 , account_id ) ;
stmt - > setString ( 3 , name ) ;
stmt - > setString ( 4 , pending_name ) ;
stmt - > setBoolean ( 5 , needs_rename ) ;
stmt - > setUInt64 ( 6 , last_login ) ;
stmt - > execute ( ) ;
}
void MySQLDatabase : : ApproveCharacterName ( uint32_t id , const std : : string & newName ) {
auto stmt = CreatePreppedStmtUnique ( " UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1 " ) ;
stmt - > setString ( 1 , newName ) ;
stmt - > setUInt64 ( 2 , time ( NULL ) ) ;
stmt - > setUInt ( 3 , id ) ;
stmt - > execute ( ) ;
}
2023-10-10 00:40:48 +00:00
2023-10-10 09:22:31 +00:00
void MySQLDatabase : : SetPendingCharacterName ( uint32_t id , const std : : string & pendingName ) {
auto stmt = CreatePreppedStmtUnique ( " UPDATE charinfo SET pending_name=?, needs_rename=0, last_login=? WHERE id=? LIMIT 1; " ) ;
stmt - > setString ( 1 , pendingName ) ;
stmt - > setUInt64 ( 2 , time ( NULL ) ) ;
stmt - > setUInt ( 3 , id ) ;
stmt - > execute ( ) ;
}
void MySQLDatabase : : UpdateCharacterLastLogin ( uint32_t id , uint64_t time ) {
auto stmt = CreatePreppedStmtUnique ( " UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1; " ) ;
stmt - > setUInt64 ( 1 , time ) ;
stmt - > setUInt ( 2 , id ) ;
stmt - > execute ( ) ;
2023-10-10 00:40:48 +00:00
}
void MySQLDatabase : : DeleteCharacter ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " DELETE FROM charxml WHERE id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM command_log WHERE character_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM friends WHERE player_id = ? OR friend_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > setUInt ( 2 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM leaderboard WHERE character_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id = ?) " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM properties WHERE owner_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM ugc WHERE character_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM activity_log WHERE character_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM mail WHERE receiver_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
stmt = CreatePreppedStmtUnique ( " DELETE FROM charinfo WHERE id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
}
AccountInfo MySQLDatabase : : GetAccountByName ( const std : : string & name ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT id FROM accounts WHERE name = ? LIMIT 1; " ) ;
stmt - > setString ( 1 , name ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
return GetAccountByID ( res - > getUInt ( " id " ) ) ;
}
return AccountInfo { } ;
}
bool MySQLDatabase : : AreBestFriends ( uint32_t goon1 , uint32_t goon2 ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1; " ) ;
stmt - > setUInt ( 1 , goon1 ) ;
stmt - > setUInt ( 2 , goon2 ) ;
stmt - > setUInt ( 3 , goon2 ) ;
stmt - > setUInt ( 4 , goon1 ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
return res - > getInt ( " best_friend " ) = = 3 ;
}
return false ;
}
AccountInfo MySQLDatabase : : GetAccountByID ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT name, password, gm_level, locked, banned, play_key_id, created_at, mute_expire FROM accounts WHERE id = ? LIMIT 1; " ) ;
stmt - > setUInt ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
AccountInfo info { } ;
info . ID = id ;
info . Name = res - > getString ( " name " ) ;
info . Password = res - > getString ( " password " ) ;
2023-10-10 09:22:31 +00:00
info . MaxGMLevel = res - > getUInt ( " gm_level " ) ;
2023-10-10 00:40:48 +00:00
info . Locked = res - > getBoolean ( " locked " ) ;
info . Banned = res - > getBoolean ( " banned " ) ;
info . PlayKeyID = res - > getUInt ( " play_key_id " ) ;
info . CreatedAt = res - > getUInt64 ( " created_at " ) ;
info . MuteExpire = res - > getUInt64 ( " mute_expire " ) ;
return info ;
}
return AccountInfo { } ;
}
std : : vector < CharacterInfo > MySQLDatabase : : GetAllCharactersByAccountID ( uint32_t accountId ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT id FROM charinfo WHERE account_id = ? LIMIT 4; " ) ;
stmt - > setUInt ( 1 , accountId ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
std : : vector < CharacterInfo > characters ;
while ( res - > next ( ) ) {
2023-10-10 09:22:31 +00:00
characters . push_back ( GetCharacterInfoByID ( res - > getUInt ( " id " ) ) ) ;
2023-10-10 00:40:48 +00:00
}
return characters ;
}
void MySQLDatabase : : CreatePetName ( uint64_t id , const std : : string & name , bool approved ) {
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO pet_names (id, name, approved) VALUES (?, ?, ?); " ) ;
stmt - > setUInt64 ( 1 , id ) ;
stmt - > setString ( 2 , name ) ;
stmt - > setBoolean ( 3 , approved ) ;
stmt - > execute ( ) ;
}
void MySQLDatabase : : DeletePetName ( uint64_t id ) {
auto stmt = CreatePreppedStmtUnique ( " DELETE FROM pet_names WHERE id = ?; " ) ;
stmt - > setUInt64 ( 1 , id ) ;
stmt - > execute ( ) ;
}
PetName MySQLDatabase : : GetPetName ( uint64_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT name, approved FROM pet_names WHERE id = ? LIMIT 1; " ) ;
stmt - > setUInt64 ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
PetName name { } ;
name . ID = id ;
name . Name = res - > getString ( " name " ) ;
name . Approved = res - > getBoolean ( " approved " ) ;
return name ;
}
return PetName { } ;
}
bool MySQLDatabase : : IsKeyActive ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT * FROM play_keys WHERE id = ? AND active = 1 LIMIT 1; " ) ;
stmt - > setUInt ( 1 , id ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
return true ;
}
return false ;
}
uint32_t MySQLDatabase : : GetObjectIDTracker ( ) {
auto stmt = CreatePreppedStmtUnique ( " SELECT last_object_id FROM object_id_tracker; " ) ;
auto res = GetResultsOfStatement ( stmt . get ( ) ) ;
while ( res - > next ( ) ) {
uint32_t id = res - > getUInt ( " last_object_id " ) ;
return id ;
}
auto stmt = CreatePreppedStmtUnique ( " INSERT INTO object_id_tracker (last_object_id) VALUES (1); " ) ;
stmt - > execute ( ) ;
return 1 ;
}
void MySQLDatabase : : SetObjectIDTracker ( uint32_t id ) {
auto stmt = CreatePreppedStmtUnique ( " UPDATE object_id_tracker SET last_object_id = ?; " ) ;
stmt - > setUInt ( 1 , id ) ;
stmt - > execute ( ) ;
}