diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 38910823..7b814d61 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -55,34 +55,10 @@ int main(int argc, char** argv) { Game::logger->Log("AuthServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); Game::logger->Log("AuthServer", "Compiled on: %s", __TIMESTAMP__); - //Connect to the MySQL Database - std::string mysql_host = Game::config->GetValue("mysql_host"); - std::string mysql_database = Game::config->GetValue("mysql_database"); - std::string mysql_username = Game::config->GetValue("mysql_username"); - std::string mysql_password = Game::config->GetValue("mysql_password"); + Database::Connect(Game::config); - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("AuthServer", "Got an error while connecting to the database: %s", ex.what()); - Database::Destroy("AuthServer"); - delete Game::server; - delete Game::logger; - return EXIT_FAILURE; - } - - //Find out the master's IP: - std::string masterIP; - uint32_t masterPort = 1500; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + // Get Master server IP and port + SocketDescriptor masterSock = Database::Connection->GetMasterServerIP(); Game::randomEngine = std::mt19937(time(0)); @@ -92,7 +68,7 @@ int main(int argc, char** argv) { if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str()); - Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::shouldShutdown); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterSock.hostAddress, masterSock.port, ServerType::Auth, Game::config, &Game::shouldShutdown); //Run it until server gets a kill message from Master: auto t = std::chrono::high_resolution_clock::now(); @@ -131,18 +107,7 @@ int main(int argc, char** argv) { //Every 10 min we ping our sql server to keep it alive hopefully: if (framesSinceLastSQLPing >= sqlPingTime) { - //Find out the master's IP for absolutely no reason: - std::string masterIP; - uint32_t masterPort; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + Database::Connection->GetMasterServerIP(); framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; @@ -151,9 +116,7 @@ int main(int argc, char** argv) { t += std::chrono::milliseconds(authFrameDelta); //Auth can run at a lower "fps" std::this_thread::sleep_until(t); } - - //Delete our objects here: - Database::Destroy("AuthServer"); + delete Game::server; delete Game::logger; delete Game::config; diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 92da9556..265b5704 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -31,16 +31,13 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) { ReadWordlistDCF("blacklist.dcf", false); } - //Read player names that are ok as well: - auto stmt = Database::CreatePreppedStmt("select name from charinfo;"); - auto res = stmt->executeQuery(); - while (res->next()) { - std::string line = res->getString(1).c_str(); - std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase - m_ApprovedWords.push_back(CalculateHash(line)); + // Read player names that are ok as well: + auto names = Database::Connection->GetAllCharacterNames(); + + for (auto& name : names) { + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + m_ApprovedWords.push_back(CalculateHash(name)); } - delete res; - delete stmt; } dChatFilter::~dChatFilter() { diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 185bccbf..6916b94d 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -77,34 +77,10 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - //Connect to the MySQL Database - std::string mysql_host = Game::config->GetValue("mysql_host"); - std::string mysql_database = Game::config->GetValue("mysql_database"); - std::string mysql_username = Game::config->GetValue("mysql_username"); - std::string mysql_password = Game::config->GetValue("mysql_password"); + Database::Connect(Game::config); - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("ChatServer", "Got an error while connecting to the database: %s", ex.what()); - Database::Destroy("ChatServer"); - delete Game::server; - delete Game::logger; - return EXIT_FAILURE; - } - - //Find out the master's IP: - std::string masterIP; - uint32_t masterPort = 1000; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + // Get Master server IP and port + SocketDescriptor masterSock = Database::Connection->GetMasterServerIP(); //It's safe to pass 'localhost' here, as the IP is only used as the external IP. uint32_t maxClients = 50; @@ -112,7 +88,7 @@ int main(int argc, char** argv) { if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str()); - Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::shouldShutdown); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterSock.hostAddress, masterSock.port, ServerType::Chat, Game::config, &Game::shouldShutdown); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(Game::config->GetValue("dont_generate_dcf")))); @@ -155,18 +131,7 @@ int main(int argc, char** argv) { //Every 10 min we ping our sql server to keep it alive hopefully: if (framesSinceLastSQLPing >= sqlPingTime) { - //Find out the master's IP for absolutely no reason: - std::string masterIP; - uint32_t masterPort; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + Database::Connection->GetMasterServerIP(); framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; @@ -176,8 +141,8 @@ int main(int argc, char** argv) { std::this_thread::sleep_until(t); } - //Delete our objects here: - Database::Destroy("ChatServer"); + // Delete our objects here: + Database::Destroy(); delete Game::server; delete Game::logger; delete Game::config; diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index e49630ed..da150f6d 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -41,14 +41,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) { mPlayers.insert(std::make_pair(data->playerID, data)); Game::logger->Log("PlayerContainer", "Added user: %s (%llu), zone: %i", data->playerName.c_str(), data->playerID, data->zoneID.GetMapID()); - auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);"); - - insertLog->setInt(1, data->playerID); - insertLog->setInt(2, 0); - insertLog->setUInt64(3, time(nullptr)); - insertLog->setInt(4, data->zoneID.GetMapID()); - - insertLog->executeUpdate(); + Database::Connection->InsertIntoActivityLog(data->playerID, 0, time(nullptr), data->zoneID.GetMapID()); } void PlayerContainer::RemovePlayer(Packet* packet) { @@ -85,14 +78,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) { Game::logger->Log("PlayerContainer", "Removed user: %llu", playerID); mPlayers.erase(playerID); - auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);"); - - insertLog->setInt(1, playerID); - insertLog->setInt(2, 1); - insertLog->setUInt64(3, time(nullptr)); - insertLog->setInt(4, player->zoneID.GetMapID()); - - insertLog->executeUpdate(); + Database::Connection->InsertIntoActivityLog(playerID, 1, time(nullptr), player->zoneID.GetMapID()); } void PlayerContainer::MuteUpdate(Packet* packet) { diff --git a/dDatabase/CMakeLists.txt b/dDatabase/CMakeLists.txt index 9e55fbe2..20906623 100644 --- a/dDatabase/CMakeLists.txt +++ b/dDatabase/CMakeLists.txt @@ -9,5 +9,11 @@ foreach(file ${DDATABASE_TABLES_SOURCES}) set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Tables/${file}") endforeach() +add_subdirectory(Databases) + +foreach (file ${DDATABASE_DATABASES_SOURCES}) + set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Databases/${file}") +endforeach() + add_library(dDatabase STATIC ${DDATABASE_SOURCES}) target_link_libraries(dDatabase sqlite3 mariadbConnCpp) diff --git a/dDatabase/Database.cpp b/dDatabase/Database.cpp index 1ce6966f..35c67b5f 100644 --- a/dDatabase/Database.cpp +++ b/dDatabase/Database.cpp @@ -1,118 +1,26 @@ #include "Database.h" -#include "Game.h" + #include "dConfig.h" -#include "dLogger.h" -using namespace std; -#pragma warning (disable:4251) //Disables SQL warnings +#include "Databases/MySQL.h" -sql::Driver* Database::driver; -sql::Connection* Database::con; -sql::Properties Database::props; -std::string Database::database; +void Database::Connect(dConfig* config) { + bool useSqlite = true; + if (config->GetValue("mysql_host") != "" && config->GetValue("mysql_database") != "" && config->GetValue("mysql_username") != "" && config->GetValue("mysql_password") != "") { + useSqlite = false; + } -void Database::Connect(const string& host, const string& database, const string& username, const string& password) { + if (useSqlite) { - //To bypass debug issues: - const char* szDatabase = database.c_str(); - const char* szUsername = username.c_str(); - const char* szPassword = password.c_str(); - - 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 (host.find(UNIX_PROTO) == 0) { - properties["hostName"] = "unix://localhost"; - properties["localSocket"] = host.substr(UNIX_PROTO.length()).c_str(); - } else if (host.find(PIPE_PROTO) == 0) { - properties["hostName"] = "pipe://localhost"; - properties["pipe"] = host.substr(PIPE_PROTO.length()).c_str(); - } else { - properties["hostName"] = host.c_str(); - } - properties["user"] = szUsername; - properties["password"] = szPassword; - properties["autoReconnect"] = "true"; - - Database::props = properties; - Database::database = database; - - Database::Connect(); -} - -void Database::Connect() { - // `connect(const Properties& props)` segfaults in windows debug, but - // `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly - if (Database::props.find("localSocket") != Database::props.end() || Database::props.find("pipe") != Database::props.end()) { - con = driver->connect(Database::props); } else { - con = driver->connect(Database::props["hostName"].c_str(), Database::props["user"].c_str(), Database::props["password"].c_str()); - } - con->setSchema(Database::database.c_str()); -} - -void Database::Destroy(std::string source, bool log) { - if (!con) return; - - if (log) { - if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!", source.c_str()); - else Game::logger->Log("Database", "Destroying MySQL connection!"); + Database::Connection = new MySQLDatabase(config->GetValue("mysql_host"), config->GetValue("mysql_database"), config->GetValue("mysql_username"), config->GetValue("mysql_password")); + Database::ConnectionType = eConnectionTypes::MYSQL; } - con->close(); - delete con; -} //Destroy - -sql::Statement* Database::CreateStmt() { - sql::Statement* toReturn = con->createStatement(); - return toReturn; -} //CreateStmt - -sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) { - const char* test = query.c_str(); - size_t size = query.length(); - sql::SQLString str(test, size); - - if (!con) { - Connect(); - Game::logger->Log("Database", "Trying to reconnect to MySQL"); - } - - if (!con->isValid() || con->isClosed()) { - delete con; - - con = nullptr; - - Connect(); - Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection"); - } - - auto* stmt = con->prepareStatement(str); - - return stmt; -} //CreatePreppedStmt - -void Database::Commit() { - Database::con->commit(); + Database::Connection->Connect(); } -bool Database::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 con->getAutoCommit(); -} - -void Database::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. - Database::con->setAutoCommit(value); +void Database::Destroy() { + Database::Connection->Destroy(); + delete Database::Connection; } diff --git a/dDatabase/Database.h b/dDatabase/Database.h index f4d13da3..8e5cc76a 100644 --- a/dDatabase/Database.h +++ b/dDatabase/Database.h @@ -1,31 +1,16 @@ #pragma once #include -#include -class MySqlException : public std::runtime_error { -public: - MySqlException() : std::runtime_error("MySQL error!") {} - MySqlException(const std::string& msg) : std::runtime_error(msg.c_str()) {} -}; +#include "Databases/DatabaseBase.h" + +class dConfig; class Database { -private: - static sql::Driver* driver; - static sql::Connection* con; - static sql::Properties props; - static std::string database; public: - static void Connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password); - static void Connect(); - static void Destroy(std::string source = "", bool log = true); + static DatabaseBase* Connection; + static eConnectionTypes ConnectionType; - static sql::Statement* CreateStmt(); - static sql::PreparedStatement* CreatePreppedStmt(const std::string& query); - static void Commit(); - static bool GetAutoCommit(); - static void SetAutoCommit(bool value); - - static std::string GetDatabase() { return database; } - static sql::Properties GetProperties() { return props; } + static void Connect(dConfig* config); + static void Destroy(); }; diff --git a/dDatabase/Databases/CMakeLists.txt b/dDatabase/Databases/CMakeLists.txt new file mode 100644 index 00000000..b0823b3d --- /dev/null +++ b/dDatabase/Databases/CMakeLists.txt @@ -0,0 +1 @@ +set(DDATABASE_DATABASES_SOURCES "MySQL.cpp") diff --git a/dDatabase/Databases/DatabaseBase.h b/dDatabase/Databases/DatabaseBase.h new file mode 100644 index 00000000..479d2662 --- /dev/null +++ b/dDatabase/Databases/DatabaseBase.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include "RakNetTypes.h" + +#include "Structures.h" + +enum eConnectionTypes { + NONE, + MYSQL, + SQLITE +}; + +class DatabaseBase { +public: + virtual void Connect() = 0; + virtual void Destroy() = 0; + + // Server Get + virtual SocketDescriptor GetMasterServerIP() = 0; + + // Server Set + virtual void CreateServer(const std::string& name, const std::string& ip, uint16_t port, uint32_t state, uint32_t version) = 0; + virtual void SetServerIpAndPortByName(const std::string& name, const std::string& ip, uint16_t port) = 0; + + // Misc + virtual void InsertIntoActivityLog(uint32_t playerId, uint32_t activityId, uint32_t timestamp, uint32_t zoneId) = 0; + virtual void InsertIntoCommandLog(uint32_t playerId, const std::string& command) = 0; + + // Character Get + virtual CharacterInfo GetCharacterInfoByID(uint32_t id) = 0; + virtual CharacterInfo GetCharacterInfoByName(const std::string& name) = 0; + virtual std::string GetCharacterXMLByID(uint32_t id) = 0; + virtual std::vector GetAllCharacterNames() = 0; + virtual std::vector GetAllCharactersByAccountID(uint32_t accountId) = 0; + virtual bool IsCharacterNameAvailable(const std::string& name) = 0; + + // Charater Write + virtual void CreateCharacterXML(uint32_t id, const std::string& xml) = 0; + virtual void UpdateCharacterXML(uint32_t id, const std::string& xml) = 0; + virtual void CreateCharacter(uint32_t id, uint32_t account_id, const std::string& name, const std::string& pending_name, bool needs_rename, uint64_t last_login) = 0; + + // Character Delete + virtual void DeleteCharacter(uint32_t id) = 0; + + // Friends Get + virtual bool AreBestFriends(uint32_t charId1, uint32_t charId2) = 0; + + // Account Get + virtual AccountInfo GetAccountByName(const std::string& name) = 0; + virtual AccountInfo GetAccountByID(uint32_t id) = 0; + virtual uint32_t GetLatestCharacterOfAccount(uint32_t id) = 0; + + // Pet Write + virtual void CreatePetName(uint64_t id, const std::string& name, bool approved) = 0; + + // Pet Delete + virtual void DeletePetName(uint64_t id) = 0; + + // Pet Get + virtual PetName GetPetName(uint64_t id) = 0; + + // Keys Get + virtual bool IsKeyActive(uint32_t id) = 0; + + // Object ID tracker Get + virtual uint32_t GetObjectIDTracker() = 0; + + // Object ID tracker Set + virtual void SetObjectIDTracker(uint32_t id) = 0; +private: + +}; diff --git a/dDatabase/Databases/MySQL.cpp b/dDatabase/Databases/MySQL.cpp new file mode 100644 index 00000000..74fa7280 --- /dev/null +++ b/dDatabase/Databases/MySQL.cpp @@ -0,0 +1,477 @@ +#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() { + try { + this->m_Driver = get_m_Driver_instance(); + this->m_Properties["hostName"] = this->m_Host; + this->m_Properties["userName"] = this->m_Username; + this->m_Properties["password"] = this->m_Password; + this->m_Properties["OPT_RECONNECT"] = true; + + this->m_Connection = this->m_Driver->connect(this->props); + this->m_Connection->setSchema(this->m_Database); + + } catch (sql::SQLException& e) { + throw MySqlException(e.what()); + } + + if (this->m_Properties.find("localSocket") != this->m_Properties.end() || this->m_Properties.find("pipe") != Database::props.end()) { + 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() { + sql::Statement* toReturn = con->createStatement(); + 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"); + } + + if (!con->isValid() || con->isClosed()) { + 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 MySQLDatabase::CreatePreppedStmtUnique(const std::string& query) { + return CreatePreppedStmt(query); +} + +std::unique_ptr MySQLDatabase::GetResultsOfStatement(sql::Statement* stmt) { + std::unique_ptr result(stmt->executeQuery()); + return result; +} + + +void MySQLDatabase::Commit() { + Database::con->commit(); +} + +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 MySQLDatabase::GetAllCharacterNames() { + auto stmt = CreatePreppedStmtUnique("SELECT name FROM charinfo;"); + auto res = GetResultsOfStatement(stmt.get()); + + std::vector names; + + while (res->next()) { + names.push_back(res->getString("name")); + } + + 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"); + info.PermissionMap = res->getUInt("permission_map"); + + 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()) { + return GetCharacterByID(res->getUInt("id")); + } + + 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()) { + return res->getString("xml_data"); + } + + return ""; +} + +void MySQLDatabase::CreateCharacterXML(uint32_t id, const std::string& xml) { + auto replaceStmt = CreatePreppedStmtUnique("INSERT INTO charxml (id, xml_data) VALUES (?, ?);"); + replaceStmt->setUInt(1, id); + replaceStmt->setString(2, xml); + + stmt->executeUpdate(); +} + +void MySQLDatabase::UpdateCharacterXML(uint32_t id, const std::string& xml) { + auto replaceStmt = CreatePreppedStmtUnique("UPDATE charxml SET xml_data = ? WHERE id = ?;"); + replaceStmt->setString(1, xml); + replaceStmt->setUInt(2, id); + + 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) { + auto replaceStmt = CreatePreppedStmtUnique("INSERT INTO charinfo (id, account_id, name, pending_name, needs_rename, last_login) VALUES (?, ?, ?, ?, ?, ?);"); + replaceStmt->setUInt(1, id); + replaceStmt->setUInt(2, account_id); + replaceStmt->setString(3, name); + replaceStmt->setString(4, pending_name); + replaceStmt->setBoolean(5, needs_rename); + replaceStmt->setUInt64(6, last_login); + + replaceStmt->executeUpdate(); +} + +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"); + info.GMLevel = res->getUInt("gm_level"); + 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 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 characters; + + while (res->next()) { + characters.push_back(GetCharacterByID(res->getUInt("id"))); + } + + 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(); +} diff --git a/dDatabase/Databases/MySQL.h b/dDatabase/Databases/MySQL.h new file mode 100644 index 00000000..555bdd55 --- /dev/null +++ b/dDatabase/Databases/MySQL.h @@ -0,0 +1,79 @@ +#pragma once + +#include "DatabaseBase.h" + +#include +#include + +class MySqlException : public std::runtime_error { +public: + MySqlException() : std::runtime_error("MySQL error!") {} + MySqlException(const std::string& msg) : std::runtime_error(msg.c_str()) {} +}; + +class MySQLDatabase : public DatabaseBase { +public: + MySQLDatabase(const std::string& host, const std::string& database, const std::string& username, const std::string& password); + ~MySQLDatabase(); + + void Connect() override; + void Destroy() override; + + sql::Statement* CreateStmt(); + sql::PreparedStatement* CreatePreppedStmt(const std::string& query); + + std::unique_ptr CreatePreppedStmtUnique(const std::string& query); + std::unique_ptr GetResultsOfStatement(sql::Statement* stmt); + + void Commit() override; + bool GetAutoCommit() override; + void SetAutoCommit(bool value) override; + + SocketDescriptor GetMasterServerIP() override; + + void CreateServer(const std::string& name, const std::string& ip, uint16_t port, uint32_t state, uint32_t version) override; + void SetServerIpAndPortByName(const std::string& name, const std::string& ip, uint16_t port) override; + + void InsertIntoActivityLog(uint32_t playerId, uint32_t activityId, uint32_t timestamp, uint32_t zoneId) override; + void InsertIntoCommandLog(uint32_t playerId, const std::string& command) override; + + CharacterInfo GetCharacterInfoByID(uint32_t id) override; + CharacterInfo GetCharacterInfoByName(const std::string& name) override; + std::string GetCharacterXMLByID(uint32_t id) override; + std::vector GetAllCharacterNames() override; + std::vector GetAllCharactersByAccountID(uint32_t accountId) override; + bool IsCharacterNameAvailable(const std::string& name) override; + + void CreateCharacterXML(uint32_t id, const std::string& xml) override; + void UpdateCharacterXML(uint32_t id, const std::string& xml) override; + void CreateCharacter(uint32_t id, uint32_t account_id, const std::string& name, const std::string& pending_name, bool needs_rename, uint64_t last_login) override; + + void DeleteCharacter(uint32_t id) override; + + bool AreBestFriends(uint32_t charId1, uint32_t charId2) override; + + AccountInfo GetAccountByName(const std::string& name) override; + AccountInfo GetAccountByID(uint32_t id) override; + uint32_t GetLatestCharacterOfAccount(uint32_t id) override; + + void CreatePetName(uint64_t id, const std::string& name, bool approved) override; + + void DeletePetName(uint64_t id) override; + + PetName GetPetName(uint64_t id) override; + + bool IsKeyActive(uint32_t id) override; + + uint32_t GetObjectIDTracker() override; + + void SetObjectIDTracker(uint32_t id) override; +private: + std::string m_Host; + std::string m_Database; + std::string m_Username; + std::string m_Password; + + sql::Connection* m_Connection; + sql::Driver* m_Driver; + sql::Properties m_Properties; +}; diff --git a/dDatabase/Databases/Structures.h b/dDatabase/Databases/Structures.h new file mode 100644 index 00000000..4fa2af66 --- /dev/null +++ b/dDatabase/Databases/Structures.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "ePermissionMap.h" + +struct CharacterInfo { + uint32_t AccountID; + uint32_t ID; + std::string Name; + std::string PendingName; + bool NameRejected; + uint32_t PropertyCloneID; + ePermissionMap PermissionMap; +}; + +struct AccountInfo { + uint32_t ID; + std::string Name; + std::string Password; + uint32_t MaxGMLevel; + bool Locked; + bool Banned; + uint32_t PlayKeyID; + uint64_t CreatedAt; + uint64_t MuteExpire; +}; + +struct PetName { + uint64_t ID; + std::string Name; + bool Approved; +}; diff --git a/dGame/Character.cpp b/dGame/Character.cpp index c5602bf2..f468281d 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -26,39 +26,16 @@ Character::Character(uint32_t id, User* parentUser) { //First load the name, etc: m_ID = id; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "SELECT name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id=? LIMIT 1;" - ); + // Load the character + auto character = Database::Connection->GetCharacterByID(m_ID); + m_Name = character.Name; + m_UnapprovedName = character.PendingName; + m_NameRejected = character.NameRejected; + m_PropertyCloneID = character.PropertyCloneID; + m_PermissionMap = character.PermissionMap; - stmt->setInt64(1, id); - - sql::ResultSet* res = stmt->executeQuery(); - - while (res->next()) { - m_Name = res->getString(1).c_str(); - m_UnapprovedName = res->getString(2).c_str(); - m_NameRejected = res->getBoolean(3); - m_PropertyCloneID = res->getUInt(4); - m_PermissionMap = static_cast(res->getUInt64(5)); - } - - delete res; - delete stmt; - - //Load the xmlData now: - sql::PreparedStatement* xmlStmt = Database::CreatePreppedStmt( - "SELECT xml_data FROM charxml WHERE id=? LIMIT 1;" - ); - - xmlStmt->setInt64(1, id); - - sql::ResultSet* xmlRes = xmlStmt->executeQuery(); - while (xmlRes->next()) { - m_XMLData = xmlRes->getString(1).c_str(); - } - - delete xmlRes; - delete xmlStmt; + // Load the xmlData now + m_XMLData = Database::Connection->GetCharacterXMLByID(m_ID); m_ZoneID = 0; //TEMP! Set back to 0 when done. This is so we can see loading screen progress for testing. m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. @@ -85,38 +62,16 @@ Character::~Character() { } void Character::UpdateFromDatabase() { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "SELECT name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id=? LIMIT 1;" - ); + // Load the character + auto character = Database::Connection->GetCharacterByID(m_ID); + m_Name = character.Name; + m_UnapprovedName = character.PendingName; + m_NameRejected = character.NameRejected; + m_PropertyCloneID = character.PropertyCloneID; + m_PermissionMap = character.PermissionMap; - stmt->setInt64(1, m_ID); - - sql::ResultSet* res = stmt->executeQuery(); - - while (res->next()) { - m_Name = res->getString(1).c_str(); - m_UnapprovedName = res->getString(2).c_str(); - m_NameRejected = res->getBoolean(3); - m_PropertyCloneID = res->getUInt(4); - m_PermissionMap = static_cast(res->getUInt64(5)); - } - - delete res; - delete stmt; - - //Load the xmlData now: - sql::PreparedStatement* xmlStmt = Database::CreatePreppedStmt( - "SELECT xml_data FROM charxml WHERE id=? LIMIT 1;" - ); - xmlStmt->setInt64(1, m_ID); - - sql::ResultSet* xmlRes = xmlStmt->executeQuery(); - while (xmlRes->next()) { - m_XMLData = xmlRes->getString(1).c_str(); - } - - delete xmlRes; - delete xmlStmt; + // Load the xmlData now + m_XMLData = Database::Connection->GetCharacterXMLByID(m_ID); m_ZoneID = 0; //TEMP! Set back to 0 when done. This is so we can see loading screen progress for testing. m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. @@ -404,18 +359,13 @@ void Character::SetIsNewLogin() { } void Character::WriteToDatabase() { - //Dump our xml into m_XMLData: + // Dump our xml into m_XMLData: auto* printer = new tinyxml2::XMLPrinter(0, true, 0); m_Doc->Print(printer); m_XMLData = printer->CStr(); - //Finally, save to db: - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE charxml SET xml_data=? WHERE id=?"); - stmt->setString(1, m_XMLData.c_str()); - stmt->setUInt(2, m_ID); - stmt->execute(); - delete stmt; - delete printer; + // Save to DB + Database::Connection->WriteCharacterXMl(m_ID, m_XMLData); } void Character::SetPlayerFlag(const uint32_t flagId, const bool value) { diff --git a/dGame/User.cpp b/dGame/User.cpp index 55bbcc09..bc566d8a 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -27,37 +27,21 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: //This needs to be re-enabled / updated whenever the mute stuff is moved to another table. //This was only done because otherwise the website's account page dies and the website is waiting on a migration to wordpress. - //sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id, gmlevel, mute_expire FROM accounts WHERE name=? LIMIT 1;"); - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id, gm_level FROM accounts WHERE name=? LIMIT 1;"); - stmt->setString(1, username.c_str()); + auto account = Database::Connection->GetAccountByName(username); - sql::ResultSet* res = stmt->executeQuery(); - while (res->next()) { - m_AccountID = res->getUInt(1); - m_MaxGMLevel = static_cast(res->getInt(2)); - m_MuteExpire = 0; //res->getUInt64(3); - } - - delete res; - delete stmt; + m_AccountID = account.ID; + m_MaxGMLevel = static_cast(account.MaxGMLevel); + m_MuteExpire = 0; //If we're loading a zone, we'll load the last used (aka current) character: if (Game::server->GetZoneID() != 0) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE account_id=? ORDER BY last_login DESC LIMIT 1;"); - stmt->setUInt(1, m_AccountID); + uint32_t characterId = Database::Connection->GetLatestCharacterOfAccount(m_AccountID); - sql::ResultSet* res = stmt->executeQuery(); - if (res->rowsCount() > 0) { - while (res->next()) { - LWOOBJID objID = res->getUInt64(1); - Character* character = new Character(uint32_t(objID), this); - m_Characters.push_back(character); - Game::logger->Log("User", "Loaded %llu as it is the last used char", objID); - } + if (characterId != 0) { + Character* character = new Character(characterId, this); + m_Characters.push_back(character); + Game::logger->Log("User", "Loaded %u as it is the last used char", characterId); } - - delete res; - delete stmt; } } diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 90e44187..7407b52a 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -159,17 +159,7 @@ void UserManager::DeletePendingRemovals() { } bool UserManager::IsNameAvailable(const std::string& requestedName) { - bool toReturn = false; //To allow for a clean exit - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? OR pending_name=? LIMIT 1;"); - stmt->setString(1, requestedName.c_str()); - stmt->setString(2, requestedName.c_str()); - - sql::ResultSet* res = stmt->executeQuery(); - if (res->rowsCount() == 0) toReturn = true; - - delete stmt; - delete res; - return toReturn; + return Database::Connection->IsCharacterNameAvailable(requestedName); } std::string UserManager::GetPredefinedName(uint32_t firstNameIndex, uint32_t middleNameIndex, uint32_t lastNameIndex) { @@ -201,10 +191,8 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) { User* u = GetUser(sysAddr); if (!u) return; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE account_id=? ORDER BY last_login DESC LIMIT 4;"); - stmt->setUInt(1, u->GetAccountID()); + auto charInfos = Database::Connection->GetAllCharactersByAccountID(u->GetAccountID()); - sql::ResultSet* res = stmt->executeQuery(); std::vector& chars = u->GetCharacters(); for (size_t i = 0; i < chars.size(); ++i) { @@ -232,16 +220,12 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) { chars.clear(); - while (res->next()) { - LWOOBJID objID = res->getUInt64(1); - Character* character = new Character(uint32_t(objID), u); + for (const auto& info : charInfos) { + Character* character = new Character(info.ID, u); character->SetIsNewLogin(); chars.push_back(character); } - delete res; - delete stmt; - WorldPackets::SendCharacterList(sysAddr, u); } @@ -290,12 +274,8 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) //Now that the name is ok, we can get an objectID from Master: ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t objectID) { - sql::PreparedStatement* overlapStmt = Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE id = ?"); - overlapStmt->setUInt(1, objectID); - - auto* overlapResult = overlapStmt->executeQuery(); - - if (overlapResult->next()) { + auto character = Database::Connection->GetCharacterInfoByID(objectID); + if (character.AccountID != 0) { Game::logger->Log("UserManager", "Character object id unavailable, check objectidtracker!"); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); return; @@ -333,45 +313,32 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) xml3 << ""; - //Check to see if our name was pre-approved: + // Check to see if our name was pre-approved bool nameOk = IsNamePreapproved(name); - if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; + if (u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; if (name != "") { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("INSERT INTO `charinfo`(`id`, `account_id`, `name`, `pending_name`, `needs_rename`, `last_login`) VALUES (?,?,?,?,?,?)"); - stmt->setUInt(1, objectID); - stmt->setUInt(2, u->GetAccountID()); - stmt->setString(3, predefinedName.c_str()); - stmt->setString(4, name.c_str()); - stmt->setBoolean(5, false); - stmt->setUInt64(6, time(NULL)); - - if (nameOk) { - stmt->setString(3, name.c_str()); - stmt->setString(4, ""); - } - - stmt->execute(); - delete stmt; + Database::Connection->CreateCharacter( + objectID, + u->GetAccountID(), + nameOk ? name : predefinedName, + nameOk ? "" : name, + false, + time(NULL) + ); } else { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("INSERT INTO `charinfo`(`id`, `account_id`, `name`, `pending_name`, `needs_rename`, `last_login`) VALUES (?,?,?,?,?,?)"); - stmt->setUInt(1, objectID); - stmt->setUInt(2, u->GetAccountID()); - stmt->setString(3, predefinedName.c_str()); - stmt->setString(4, ""); - stmt->setBoolean(5, false); - stmt->setUInt64(6, time(NULL)); - - stmt->execute(); - delete stmt; + Database::Connection->CreateCharacter( + objectID, + u->GetAccountID(), + predefinedName, + "", + false, + time(NULL) + ); } - //Now finally insert our character xml: - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("INSERT INTO `charxml`(`id`, `xml_data`) VALUES (?,?)"); - stmt->setUInt(1, objectID); - stmt->setString(2, xml3.str().c_str()); - stmt->execute(); - delete stmt; + // Now finally insert our character xml + Database::Connection->CreateCharacterXML(objectID, xml3.str()); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); @@ -403,73 +370,13 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) WorldPackets::SendCharacterDeleteResponse(sysAddr, false); } else { Game::logger->Log("UserManager", "Deleting character %i", charID); - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM charxml WHERE id=? LIMIT 1;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM command_log WHERE character_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM friends WHERE player_id=? OR friend_id=?;"); - stmt->setUInt(1, charID); - stmt->setUInt(2, charID); - stmt->execute(); - delete stmt; - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION); - bitStream.Write(objectID); - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM leaderboard WHERE character_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);" - ); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM properties WHERE owner_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM ugc WHERE character_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM activity_log WHERE character_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM mail WHERE receiver_id=?;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM charinfo WHERE id=? LIMIT 1;"); - stmt->setUInt64(1, charID); - stmt->execute(); - delete stmt; - } + + Database::Connection->DeleteCharacter(charID); + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION); + bitStream.Write(objectID); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); WorldPackets::SendCharacterDeleteResponse(sysAddr, true); } diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 5132af08..35d501f4 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -1083,44 +1083,23 @@ PetComponent::~PetComponent() { } void PetComponent::SetPetNameForModeration(const std::string& petName) { - int approved = 1; //default, in mod + int approved = 1; // default, in mod - //Make sure that the name isn't already auto-approved: + // Make sure that the name isn't already auto-approved if (Game::chatFilter->IsSentenceOkay(petName, eGameMasterLevel::CIVILIAN).empty()) { - approved = 2; //approved + approved = 2; } - auto deleteStmt = Database::CreatePreppedStmt("DELETE FROM pet_names WHERE id = ? LIMIT 1;"); - deleteStmt->setUInt64(1, m_DatabaseId); + Database::Connection->DeletePetName(m_DatabaseId); - deleteStmt->execute(); - - delete deleteStmt; - - //Save to db: - auto stmt = Database::CreatePreppedStmt("INSERT INTO `pet_names` (`id`, `pet_name`, `approved`) VALUES (?, ?, ?);"); - stmt->setUInt64(1, m_DatabaseId); - stmt->setString(2, petName); - stmt->setInt(3, approved); - stmt->execute(); - delete stmt; + // Save to db + Database::Connection->CreatePetName(m_DatabaseId, petName, approved); } void PetComponent::LoadPetNameFromModeration() { - auto stmt = Database::CreatePreppedStmt("SELECT pet_name, approved FROM pet_names WHERE id = ? LIMIT 1;"); - stmt->setUInt64(1, m_DatabaseId); - - auto res = stmt->executeQuery(); - while (res->next()) { - m_ModerationStatus = res->getInt(2); - - if (m_ModerationStatus == 2) { - m_Name = res->getString(1); - } - } - - delete res; - delete stmt; + auto petNameInfo = Database::Connection->GetPetName(m_DatabaseId); + m_ModerationStatus = petNameInfo.Approved; + m_Name = petNameInfo.Name; } void PetComponent::SetPreconditions(std::string& preconditions) { diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 17fd980c..404700f9 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -360,11 +360,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } // Log command to database - auto stmt = Database::CreatePreppedStmt("INSERT INTO command_log (character_id, command) VALUES (?, ?);"); - stmt->setInt(1, entity->GetCharacter()->GetID()); - stmt->setString(2, GeneralUtils::UTF16ToWTF8(command).c_str()); - stmt->execute(); - delete stmt; + Database::Connection->InsertIntoCommandLog(entity->GetCharacter()->GetID(), GeneralUtils::UTF16ToWTF8(command)); if (chatCommand == "setminifig" && args.size() == 2 && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { // could break characters so only allow if GM > 0 int32_t minifigItemId; @@ -816,18 +812,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "mailitem" && entity->GetGMLevel() >= eGameMasterLevel::MODERATOR && args.size() >= 2) { const auto& playerName = args[0]; - - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id from charinfo WHERE name=? LIMIT 1;"); - stmt->setString(1, playerName); - sql::ResultSet* res = stmt->executeQuery(); - uint32_t receiverID = 0; - - if (res->rowsCount() > 0) { - while (res->next()) receiverID = res->getUInt(1); - } - - delete stmt; - delete res; + + auto character = Database::Connection->GetCharacterByName(playerName); + uint32_t receiverID = character.AccountID; if (receiverID == 0) { ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); @@ -1016,26 +1003,15 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LWOOBJID characterId = 0; if (player == nullptr) { - auto* accountQuery = Database::CreatePreppedStmt("SELECT account_id, id FROM charinfo WHERE name=? LIMIT 1;"); + auto character = Database::Connection->GetCharacterByName(args[0]); - accountQuery->setString(1, args[0]); - - auto result = accountQuery->executeQuery(); - - if (result->rowsCount() > 0) { - while (result->next()) { - accountId = result->getUInt(1); - characterId = result->getUInt64(2); - - GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); - } - } - - delete accountQuery; - delete result; - - if (accountId == 0) { + if (accountId != 0) { + accountId = character.AccountID; + characterId = character.ID; + + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); + } else { ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); return; @@ -1128,20 +1104,10 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit uint32_t accountId = 0; if (player == nullptr) { - auto* accountQuery = Database::CreatePreppedStmt("SELECT account_id FROM charinfo WHERE name=? LIMIT 1;"); + auto character = Database::Connection->GetCharacterInfoByName(args[0]); + accountId = character.AccountID; - accountQuery->setString(1, args[0]); - - auto result = accountQuery->executeQuery(); - - if (result->rowsCount() > 0) { - while (result->next()) accountId = result->getUInt(1); - } - - delete accountQuery; - delete result; - - if (accountId == 0) { + if (character.AccountID == 0) { ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); return; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 94257471..485ecb6c 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -129,19 +129,7 @@ int main(int argc, char** argv) { Game::logger->Log("MasterServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); Game::logger->Log("MasterServer", "Compiled on: %s", __TIMESTAMP__); - //Connect to the MySQL Database - std::string mysql_host = Game::config->GetValue("mysql_host"); - std::string mysql_database = Game::config->GetValue("mysql_database"); - std::string mysql_username = Game::config->GetValue("mysql_username"); - std::string mysql_password = Game::config->GetValue("mysql_password"); - - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("MasterServer", "Got an error while connecting to the database: %s", ex.what()); - Game::logger->Log("MigrationRunner", "Migrations not run"); - return EXIT_FAILURE; - } + Database::Connect(Game::config); try { std::string clientPathStr = Game::config->GetValue("client_location"); @@ -313,8 +301,7 @@ int main(int argc, char** argv) { Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::shouldShutdown); //Query for the database for a server labeled "master" - auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); - auto* result = masterLookupStatement->executeQuery(); + auto masterServerSock = Database::Connection->GetMasterServerIP(); auto master_server_ip = Game::config->GetValue("master_ip"); @@ -323,20 +310,11 @@ int main(int argc, char** argv) { } //If we found a server, update it's IP and port to the current one. - if (result->next()) { - auto* updateStatement = Database::CreatePreppedStmt("UPDATE `servers` SET `ip` = ?, `port` = ? WHERE `id` = ?"); - updateStatement->setString(1, master_server_ip.c_str()); - updateStatement->setInt(2, Game::server->GetPort()); - updateStatement->setInt(3, result->getInt("id")); - updateStatement->execute(); - delete updateStatement; + if (masterServerSock.port != 0) { + Database::Connection->SetServerIpAndPortByName("master", master_server_ip, Game::server->GetPort()); } else { - //If we didn't find a server, create one. - auto* insertStatement = Database::CreatePreppedStmt("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171023)"); - insertStatement->setString(1, master_server_ip.c_str()); - insertStatement->setInt(2, Game::server->GetPort()); - insertStatement->execute(); - delete insertStatement; + // If we didn't find a server, create one. + Database::Connection->CreateServer("master", master_server_ip, Game::server->GetPort(), 0, 171023); } //Create additional objects here: @@ -382,18 +360,7 @@ int main(int argc, char** argv) { //Every 10 min we ping our sql server to keep it alive hopefully: if (framesSinceLastSQLPing >= sqlPingTime) { - //Find out the master's IP for absolutely no reason: - std::string masterIP; - uint32_t masterPort; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + Database::Connection->GetMasterServerIP(); framesSinceLastSQLPing = 0; } else @@ -973,8 +940,8 @@ void ShutdownSequence(int32_t signal) { } int32_t FinalizeShutdown(int32_t signal) { - //Delete our objects here: - Database::Destroy("MasterServer"); + // Delete our objects here + Database::Destroy(); if (Game::config) delete Game::config; if (Game::im) delete Game::im; if (Game::server) delete Game::server; diff --git a/dMasterServer/ObjectIDManager.cpp b/dMasterServer/ObjectIDManager.cpp index 83dde8dd..4d5a54bf 100644 --- a/dMasterServer/ObjectIDManager.cpp +++ b/dMasterServer/ObjectIDManager.cpp @@ -10,62 +10,18 @@ ObjectIDManager* ObjectIDManager::m_Address = nullptr; //! Initializes the manager void ObjectIDManager::Initialize(dLogger* logger) { this->mLogger = logger; - this->currentPersistentID = 1; - - try { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "SELECT last_object_id FROM object_id_tracker"); - - sql::ResultSet* result = stmt->executeQuery(); - auto next = result->next(); - - if (!next) { - sql::PreparedStatement* insertStmt = Database::CreatePreppedStmt( - "INSERT INTO object_id_tracker VALUES (1)"); - - insertStmt->execute(); - - delete insertStmt; - - return; - } - - while (next) { - this->currentPersistentID = - result->getInt(1) > 0 ? result->getInt(1) : 1; - next = result->next(); - } - - delete result; - delete stmt; - } catch (sql::SQLException& e) { - mLogger->Log("ObjectIDManager", "Unable to fetch max persistent object " - "ID in use. Defaulting to 1."); - mLogger->Log("ObjectIDManager", "SQL error: %s", e.what()); - this->currentPersistentID = 1; - } + this->currentPersistentID = Database::Connection->GetObjectIDTracker(); } //! Generates a new persistent ID uint32_t ObjectIDManager::GeneratePersistentID(void) { uint32_t toReturn = ++this->currentPersistentID; - // So we peroidically save our ObjID to the database: - // if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT! - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "UPDATE object_id_tracker SET last_object_id=?"); - stmt->setUInt(1, toReturn); - stmt->execute(); - delete stmt; - // } + Database::Connection->SetObjectIDTracker(toReturn); return toReturn; } void ObjectIDManager::SaveToDatabase() { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt( - "UPDATE object_id_tracker SET last_object_id=?"); - stmt->setUInt(1, currentPersistentID); - stmt->execute(); - delete stmt; + Database::Connection->SetObjectIDTracker(this->currentPersistentID); } diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 3fe9b35b..7330ace3 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -64,122 +64,57 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { const char* szUsername = username.c_str(); // Fetch account details - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT password, banned, locked, play_key_id, gm_level FROM accounts WHERE name=? LIMIT 1;"); - stmt->setString(1, szUsername); + auto account = Database::Connection->GetAccountByName(username); - sql::ResultSet* res = stmt->executeQuery(); - - if (res->rowsCount() == 0) { + if (account.ID == 0) { server->GetLogger()->Log("AuthPackets", "No user found!"); AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username); return; } - - std::string sqlPass = ""; - bool sqlBanned = false; - bool sqlLocked = false; - uint32_t sqlPlayKey = 0; - uint32_t sqlGmLevel = 0; - - while (res->next()) { - sqlPass = res->getString(1).c_str(); - sqlBanned = res->getBoolean(2); - sqlLocked = res->getBoolean(3); - sqlPlayKey = res->getInt(4); - sqlGmLevel = res->getInt(5); - } - - delete stmt; - delete res; + //If we aren't running in live mode, then only GMs are allowed to enter: const auto& closedToNonDevs = Game::config->GetValue("closed_to_non_devs"); - if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && sqlGmLevel == 0) { + if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && account.MaxGMLevel == 0) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username); return; } if (Game::config->GetValue("dont_use_keys") != "1") { - //Check to see if we have a play key: - if (sqlPlayKey == 0 && sqlGmLevel == 0) { + // Check to see if we have a play key + if (account.PlayKeyID == 0 && account.MaxGMLevel == 0) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username); server->GetLogger()->Log("AuthPackets", "User %s tried to log in, but they don't have a play key.", username.c_str()); return; } - //Check if the play key is _valid_: - auto keyCheckStmt = Database::CreatePreppedStmt("SELECT active FROM `play_keys` WHERE id=?"); - keyCheckStmt->setInt(1, sqlPlayKey); - auto keyRes = keyCheckStmt->executeQuery(); - bool isKeyActive = false; + // Check if the play key is _valid_ + bool isKeyActive = Database::Connection->IsKeyActive(account.PlayKeyID); - if (keyRes->rowsCount() == 0 && sqlGmLevel == 0) { + if (!isKeyActive && account.MaxGMLevel == 0) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username); - return; - } - - while (keyRes->next()) { - isKeyActive = (bool)keyRes->getInt(1); - } - - if (!isKeyActive && sqlGmLevel == 0) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username); - server->GetLogger()->Log("AuthPackets", "User %s tried to log in, but their play key was disabled", username.c_str()); + server->GetLogger()->Log("AuthPackets", "User %s tried to log in, but they lacked a play key", username.c_str()); return; } } - if (sqlBanned) { + if (account.Banned) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, "", "", 2001, username); return; } - if (sqlLocked) { + if (account.Locked) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username); return; } - /* - * Updated hashing method: - * First attempt bcrypt. - * If that fails, fallback to old method and setup bcrypt for new login. - */ - - bool loginSuccess = true; - - int32_t bcryptState = ::bcrypt_checkpw(password.c_str(), sqlPass.c_str()); - - if (bcryptState != 0) { - // Fallback on old method - - std::string oldPassword = sha512(password + username); - - if (sqlPass != oldPassword) { - loginSuccess = false; - } else { - // Generate new hash for bcrypt - - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - - bcryptState = ::bcrypt_gensalt(12, salt); - - assert(bcryptState == 0); - - bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash); - - assert(bcryptState == 0); - - sql::PreparedStatement* accountUpdate = Database::CreatePreppedStmt("UPDATE accounts SET password = ? WHERE name = ? LIMIT 1;"); - - accountUpdate->setString(1, std::string(hash, BCRYPT_HASHSIZE).c_str()); - accountUpdate->setString(2, szUsername); - - accountUpdate->executeUpdate(); + if (!account.Password.empty()) { + if (account.Password[0] != '$') { + Game::logger->Log("AuthPackets", "Invalid password being parsed for user %s", username.c_str()); } - } else { - // Login success with bcrypt } - if (!loginSuccess) { + int32_t bcryptState = ::bcrypt_checkpw(password.c_str(), account.Password.c_str()); + + if (bcryptState != 0) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username); server->GetLogger()->Log("AuthPackets", "Wrong password used"); } else { diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index e797ea21..ec88a307 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -351,41 +351,17 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa // Private chat LWOOBJID idOfReceiver = LWOOBJID_EMPTY; - { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE name = ?"); - stmt->setString(1, receiver); - - sql::ResultSet* res = stmt->executeQuery(); - - if (res->next()) { - idOfReceiver = res->getInt("id"); - } - - delete stmt; - delete res; - } - + auto receiverInfo = Database::Connection->GetCharacterInfoByName(receiver); + idOfReceiver = receiverInfo.ID; + if (user->GetIsBestFriendMap().find(receiver) == user->GetIsBestFriendMap().end() && idOfReceiver != LWOOBJID_EMPTY) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"); - stmt->setInt(1, entity->GetObjectID()); - stmt->setInt(2, idOfReceiver); - stmt->setInt(3, idOfReceiver); - stmt->setInt(4, entity->GetObjectID()); - - sql::ResultSet* res = stmt->executeQuery(); - - if (res->next()) { - isBestFriend = res->getInt("best_friend") == 3; - } + isBestFriend = Database::Connection->AreBestFriends(entity->GetObjectID(), idOfReceiver); if (isBestFriend) { auto tmpBestFriendMap = user->GetIsBestFriendMap(); tmpBestFriendMap[receiver] = true; user->SetIsBestFriendMap(tmpBestFriendMap); } - - delete res; - delete stmt; } else if (user->GetIsBestFriendMap().find(receiver) != user->GetIsBestFriendMap().end()) { isBestFriend = true; } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 0803bbf7..2db2a3e6 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -180,43 +180,21 @@ int main(int argc, char** argv) { CDClientManager::Instance(); - //Connect to the MySQL Database - std::string mysql_host = Game::config->GetValue("mysql_host"); - std::string mysql_database = Game::config->GetValue("mysql_database"); - std::string mysql_username = Game::config->GetValue("mysql_username"); - std::string mysql_password = Game::config->GetValue("mysql_password"); - Diagnostics::SetProduceMemoryDump(Game::config->GetValue("generate_dump") == "1"); if (!Game::config->GetValue("dump_folder").empty()) { Diagnostics::SetOutDirectory(Game::config->GetValue("dump_folder")); } - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("WorldServer", "Got an error while connecting to the database: %s", ex.what()); - return EXIT_FAILURE; - } + Database::Connect(Game::config); - //Find out the master's IP: - std::string masterIP = "localhost"; - uint32_t masterPort = 1000; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + auto masterSock = Database::Connection->GetMasterServerIP(); ObjectIDManager::Instance()->Initialize(); UserManager::Instance()->Initialize(); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(Game::config->GetValue("dont_generate_dcf")))); - Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, &Game::shouldShutdown, zoneID); + Game::server = new dServer(masterSock.hostAddress, ourPort, instanceID, maxClients, false, true, Game::logger, masterSock.hostAddress, masterSock.port, ServerType::World, Game::config, &Game::shouldShutdown, zoneID); //Connect to the chat server: uint32_t chatPort = 1501; @@ -225,7 +203,7 @@ int main(int argc, char** argv) { auto chatSock = SocketDescriptor(uint16_t(ourPort + 2), 0); Game::chatServer = RakNetworkFactory::GetRakPeerInterface(); Game::chatServer->Startup(1, 30, &chatSock, 1); - Game::chatServer->Connect(masterIP.c_str(), chatPort, "3.25 ND1", 8); + Game::chatServer->Connect(masterSock.hostAddress, chatPort, "3.25 ND1", 8); //Set up other things: Game::randomEngine = std::mt19937(time(0)); @@ -380,7 +358,7 @@ int main(int argc, char** argv) { if (framesSinceChatDisconnect >= chatReconnectionTime) { framesSinceChatDisconnect = 0; - Game::chatServer->Connect(masterIP.c_str(), chatPort, "3.25 ND1", 8); + Game::chatServer->Connect(masterSock.hostAddress, chatPort, "3.25 ND1", 8); } } else framesSinceChatDisconnect = 0; @@ -482,18 +460,7 @@ int main(int argc, char** argv) { //Every 10 min we ping our sql server to keep it alive hopefully: if (framesSinceLastSQLPing >= sqlPingTime) { - //Find out the master's IP for absolutely no reason: - std::string masterIP; - uint32_t masterPort; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';"); - auto res = stmt->executeQuery(); - while (res->next()) { - masterIP = res->getString(1).c_str(); - masterPort = res->getInt(2); - } - - delete res; - delete stmt; + Database::Connection->GetMasterServerIP(); framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; @@ -886,20 +853,10 @@ void HandlePacket(Packet* packet) { // If the check is turned on, validate the client's database checksum. if (Game::config->GetValue("check_fdb") == "1" && !databaseChecksum.empty()) { - uint32_t gmLevel = 0; - auto* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;"); - stmt->setString(1, username.c_str()); + auto account = Database::Connection->GetAccountByName(username); - auto* res = stmt->executeQuery(); - while (res->next()) { - gmLevel = res->getInt(1); - } - - delete stmt; - delete res; - - // Developers may skip this check - if (gmLevel < 8 && clientDatabaseChecksum != databaseChecksum) { + // Operators may skip this check + if (account.MaxGMLevel < 8 && clientDatabaseChecksum != databaseChecksum) { Game::logger->Log("WorldServer", "Client's database checksum does not match the server's, aborting connection."); Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::WRONG_GAME_VERSION); return; @@ -1354,7 +1311,8 @@ void FinalizeShutdown() { //Delete our objects here: Metrics::Clear(); - Database::Destroy("WorldServer"); + Database::Destroy(); + if (Game::chatFilter) delete Game::chatFilter; if (Game::zoneManager) delete Game::zoneManager; if (Game::server) delete Game::server; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index ad587b18..5ea4d4fb 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -1,8 +1,4 @@ -# MySQL connection info: -mysql_host= -mysql_database= -mysql_username= -mysql_password= +database_file=darkflame.sqlite # 0 or 1, should log to console log_to_console=1