diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f6d240a..635f5f44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,7 +214,12 @@ set(INCLUDED_DIRECTORIES "dNavigation/dTerrain" "dZoneManager" "dDatabase" - "dDatabase/Tables" + "dDatabase/CDClientDatabase" + "dDatabase/CDClientDatabase/CDClientTables" + "dDatabase/GameDatabase" + "dDatabase/GameDatabase/ITables" + "dDatabase/GameDatabase/MySQL" + "dDatabase/GameDatabase/MySQL/Tables" "dNet" "dScripts" "dScripts/02_server" @@ -329,8 +334,9 @@ add_subdirectory(thirdparty) file( GLOB HEADERS_DDATABASE LIST_DIRECTORIES false - ${PROJECT_SOURCE_DIR}/dDatabase/*.h - ${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.h + ${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/*.h + ${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables/*.h + ${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables/*.h ${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.h ) diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index a18657ca..c6d434b3 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -55,14 +55,8 @@ int main(int argc, char** argv) { LOG("Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); LOG("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); + Database::Connect(); } catch (sql::SQLException& ex) { LOG("Got an error while connecting to the database: %s", ex.what()); Database::Destroy("AuthServer"); @@ -74,15 +68,12 @@ int main(int argc, char** argv) { //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; + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; + } Game::randomEngine = std::mt19937(time(0)); @@ -134,16 +125,12 @@ int main(int argc, char** argv) { //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); + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; } - delete res; - delete stmt; - framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 8d2015b0..6e81db3b 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -32,15 +32,11 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) { } //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)); + auto approvedNames = Database::Get()->GetApprovedCharacterNames(); + for (auto& name : approvedNames) { + std::transform(name.begin(), name.end(), name.begin(), ::tolower); //Transform to lowercase + m_ApprovedWords.push_back(CalculateHash(name)); } - delete res; - delete stmt; } dChatFilter::~dChatFilter() { diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 72383c38..0a3caa8e 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -30,32 +30,17 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { auto player = playerContainer.GetPlayerData(playerID); if (!player) return; - //Get our friends list from the Db. Using a derived table since the friend of a player can be in either column. - std::unique_ptr stmt(Database::CreatePreppedStmt( - "SELECT fr.requested_player, best_friend, ci.name FROM " - "(SELECT CASE " - "WHEN player_id = ? THEN friend_id " - "WHEN friend_id = ? THEN player_id " - "END AS requested_player, best_friend FROM friends) AS fr " - "JOIN charinfo AS ci ON ci.id = fr.requested_player " - "WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?;")); - stmt->setUInt(1, static_cast(playerID)); - stmt->setUInt(2, static_cast(playerID)); - stmt->setUInt(3, static_cast(playerID)); - - std::vector friends; - - std::unique_ptr res(stmt->executeQuery()); - while (res->next()) { + auto friendsList = Database::Get()->GetFriendsList(playerID); + for (const auto& friendData : friendsList) { FriendData fd; fd.isFTP = false; // not a thing in DLU - fd.friendID = res->getUInt(1); + fd.friendID = friendData.friendID; GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); - fd.isBestFriend = res->getInt(2) == 3; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs + fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs if (fd.isBestFriend) player->countOfBestFriends += 1; - fd.friendName = res->getString(3); + fd.friendName = friendData.friendName; //Now check if they're online: auto fr = playerContainer.GetPlayerData(fd.friendID); @@ -71,7 +56,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { fd.zoneID = LWOZONEID(); } - friends.push_back(fd); + player->friends.push_back(fd); } //Now, we need to send the friendlist to the server they came from: @@ -83,22 +68,17 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE); bitStream.Write(0); bitStream.Write(1); //Length of packet -- just writing one as it doesn't matter, client skips it. - bitStream.Write((uint16_t)friends.size()); + bitStream.Write((uint16_t)player->friends.size()); - for (auto& data : friends) { + for (auto& data : player->friends) { data.Serialize(bitStream); } - player->friends = friends; - SystemAddress sysAddr = player->sysAddr; SEND_PACKET; } void ChatPacketHandler::HandleFriendRequest(Packet* packet) { - auto maxNumberOfBestFriendsAsString = Game::config->GetValue("max_number_of_best_friends"); - // If this config option doesn't exist, default to 5 which is what live used. - auto maxNumberOfBestFriends = maxNumberOfBestFriendsAsString != "" ? std::stoi(maxNumberOfBestFriendsAsString) : 5U; CINSTREAM_SKIP_HEADER; LWOOBJID requestorPlayerID; inStream.Read(requestorPlayerID); @@ -155,35 +135,26 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { // If at this point we dont have a target, then they arent online and we cant send the request. // Send the response code that corresponds to what the error is. if (!requestee) { - std::unique_ptr nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;")); - nameQuery->setString(1, playerName); - std::unique_ptr result(nameQuery->executeQuery()); - requestee.reset(new PlayerData()); requestee->playerName = playerName; + auto responseType = Database::Get()->GetCharacterInfo(playerName) + ? eAddFriendResponseType::NOTONLINE + : eAddFriendResponseType::INVALIDCHARACTER; - SendFriendResponse(requestor, requestee.get(), result->next() ? eAddFriendResponseType::NOTONLINE : eAddFriendResponseType::INVALIDCHARACTER); + SendFriendResponse(requestor, requestee.get(), responseType); return; } if (isBestFriendRequest) { - std::unique_ptr friendUpdate(Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;")); - friendUpdate->setUInt(1, static_cast(requestorPlayerID)); - friendUpdate->setUInt(2, static_cast(requestee->playerID)); - friendUpdate->setUInt(3, static_cast(requestee->playerID)); - friendUpdate->setUInt(4, static_cast(requestorPlayerID)); - std::unique_ptr result(friendUpdate->executeQuery()); - LWOOBJID queryPlayerID = LWOOBJID_EMPTY; - LWOOBJID queryFriendID = LWOOBJID_EMPTY; uint8_t oldBestFriendStatus{}; uint8_t bestFriendStatus{}; - - if (result->next()) { + auto bestFriendInfo = Database::Get()->GetBestFriendStatus(requestorPlayerID, requestee->playerID); + if (bestFriendInfo) { // Get the IDs - queryPlayerID = result->getInt(1); - queryFriendID = result->getInt(2); - oldBestFriendStatus = result->getInt(3); + LWOOBJID queryPlayerID = bestFriendInfo->playerCharacterId; + LWOOBJID queryFriendID = bestFriendInfo->friendCharacterId; + oldBestFriendStatus = bestFriendInfo->bestFriendStatus; bestFriendStatus = oldBestFriendStatus; // Set the bits @@ -204,22 +175,17 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { // Only do updates if there was a change in the bff status. if (oldBestFriendStatus != bestFriendStatus) { - if (requestee->countOfBestFriends >= maxNumberOfBestFriends || requestor->countOfBestFriends >= maxNumberOfBestFriends) { - if (requestee->countOfBestFriends >= maxNumberOfBestFriends) { + auto maxBestFriends = playerContainer.GetMaxNumberOfBestFriends(); + if (requestee->countOfBestFriends >= maxBestFriends || requestor->countOfBestFriends >= maxBestFriends) { + if (requestee->countOfBestFriends >= maxBestFriends) { SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false); } - if (requestor->countOfBestFriends >= maxNumberOfBestFriends) { + if (requestor->countOfBestFriends >= maxBestFriends) { SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false); } } else { // Then update the database with this new info. - std::unique_ptr updateQuery(Database::CreatePreppedStmt("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;")); - updateQuery->setUInt(1, bestFriendStatus); - updateQuery->setUInt(2, static_cast(requestorPlayerID)); - updateQuery->setUInt(3, static_cast(requestee->playerID)); - updateQuery->setUInt(4, static_cast(requestee->playerID)); - updateQuery->setUInt(5, static_cast(requestorPlayerID)); - updateQuery->executeUpdate(); + Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee->playerID, bestFriendStatus); // Sent the best friend update here if the value is 3 if (bestFriendStatus == 3U) { requestee->countOfBestFriends += 1; @@ -242,8 +208,15 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::WAITINGAPPROVAL, true, true); } } else { - // Do not send this if we are requesting to be a best friend. - SendFriendRequest(requestee.get(), requestor); + auto maxFriends = playerContainer.GetMaxNumberOfFriends(); + if (requestee->friends.size() >= maxFriends) { + SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false); + } else if (requestor->friends.size() >= maxFriends) { + SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false); + } else { + // Do not send this if we are requesting to be a best friend. + SendFriendRequest(requestee.get(), requestor); + } } // If the player is actually a player and not a ghost one defined above, release it from being deleted. @@ -314,11 +287,7 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) { requesteeData.isOnline = true; requestor->friends.push_back(requesteeData); - std::unique_ptr statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);")); - statement->setUInt(1, static_cast(requestor->playerID)); - statement->setUInt(2, static_cast(requestee->playerID)); - statement->setInt(3, 0); - statement->execute(); + Database::Get()->AddFriend(requestor->playerID, requestee->playerID); } if (serverResponseCode != eAddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends); @@ -333,25 +302,17 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) { //we'll have to query the db here to find the user, since you can delete them while they're offline. //First, we need to find their ID: - std::unique_ptr stmt(Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;")); - stmt->setString(1, friendName.c_str()); - LWOOBJID friendID = 0; - std::unique_ptr res(stmt->executeQuery()); - while (res->next()) { - friendID = res->getUInt(1); + auto friendIdResult = Database::Get()->GetCharacterInfo(friendName); + if (friendIdResult) { + friendID = friendIdResult->id; } // Convert friendID to LWOOBJID GeneralUtils::SetBit(friendID, eObjectBits::PERSISTENT); GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER); - std::unique_ptr deletestmt(Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;")); - deletestmt->setUInt(1, static_cast(playerID)); - deletestmt->setUInt(2, static_cast(friendID)); - deletestmt->setUInt(3, static_cast(friendID)); - deletestmt->setUInt(4, static_cast(playerID)); - deletestmt->execute(); + Database::Get()->RemoveFriend(playerID, friendID); //Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended: auto goonA = playerContainer.GetPlayerData(playerID); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 6fb54594..5fc861b6 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -78,13 +78,8 @@ int main(int argc, char** argv) { } //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); + Database::Connect(); } catch (sql::SQLException& ex) { LOG("Got an error while connecting to the database: %s", ex.what()); Database::Destroy("ChatServer"); @@ -96,16 +91,11 @@ int main(int argc, char** argv) { //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); + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; } - - delete res; - delete stmt; - //It's safe to pass 'localhost' here, as the IP is only used as the external IP. uint32_t maxClients = 50; uint32_t ourPort = 1501; @@ -158,15 +148,12 @@ int main(int argc, char** argv) { //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; + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; + } framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index f467cfbe..8b9eb744 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -14,10 +14,12 @@ #include "dConfig.h" PlayerContainer::PlayerContainer() { + GeneralUtils::TryParse(Game::config->GetValue("max_number_of_best_friends"), m_MaxNumberOfBestFriends); + GeneralUtils::TryParse(Game::config->GetValue("max_number_of_friends"), m_MaxNumberOfFriends); } PlayerContainer::~PlayerContainer() { - mPlayers.clear(); + m_Players.clear(); } TeamData::TeamData() { @@ -41,19 +43,12 @@ void PlayerContainer::InsertPlayer(Packet* packet) { inStream.Read(data->muteExpire); data->sysAddr = packet->systemAddress; - mNames[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName); + m_Names[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName); - mPlayers.insert(std::make_pair(data->playerID, data)); + m_Players.insert(std::make_pair(data->playerID, data)); LOG("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::Get()->UpdateActivityLog(data->playerID, eActivityType::PlayerLoggedIn, data->zoneID.GetMapID()); } void PlayerContainer::RemovePlayer(Packet* packet) { @@ -88,16 +83,9 @@ void PlayerContainer::RemovePlayer(Packet* packet) { } LOG("Removed user: %llu", playerID); - mPlayers.erase(playerID); + m_Players.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::Get()->UpdateActivityLog(playerID, eActivityType::PlayerLoggedOut, player->zoneID.GetMapID()); } void PlayerContainer::MuteUpdate(Packet* packet) { @@ -191,7 +179,7 @@ TeamData* PlayerContainer::CreateLocalTeam(std::vector members) { TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) { auto* team = new TeamData(); - team->teamID = ++mTeamIDCounter; + team->teamID = ++m_TeamIDCounter; team->leaderID = leader; team->local = local; @@ -376,15 +364,15 @@ void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) { } std::u16string PlayerContainer::GetName(LWOOBJID playerID) { - const auto& pair = mNames.find(playerID); + const auto& pair = m_Names.find(playerID); - if (pair == mNames.end()) return u""; + if (pair == m_Names.end()) return u""; return pair->second; } LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) { - for (const auto& pair : mNames) { + for (const auto& pair : m_Names) { if (pair.second == playerName) { return pair.first; } diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index a0a40058..000164ac 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -39,13 +39,13 @@ public: void BroadcastMuteUpdate(LWOOBJID player, time_t time); PlayerData* GetPlayerData(const LWOOBJID& playerID) { - auto it = mPlayers.find(playerID); - if (it != mPlayers.end()) return it->second; + auto it = m_Players.find(playerID); + if (it != m_Players.end()) return it->second; return nullptr; } PlayerData* GetPlayerData(const std::string& playerName) { - for (auto player : mPlayers) { + for (auto player : m_Players) { if (player.second) { std::string pn = player.second->playerName.c_str(); if (pn == playerName) return player.second; @@ -67,13 +67,17 @@ public: std::u16string GetName(LWOOBJID playerID); LWOOBJID GetId(const std::u16string& playerName); bool GetIsMuted(PlayerData* data); + uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } + uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } - std::map& GetAllPlayerData() { return mPlayers; } + std::map& GetAllPlayerData() { return m_Players; } private: - LWOOBJID mTeamIDCounter = 0; - std::map mPlayers; + LWOOBJID m_TeamIDCounter = 0; + std::map m_Players; std::vector mTeams; - std::unordered_map mNames; + std::unordered_map m_Names; + uint32_t m_MaxNumberOfBestFriends = 5; + uint32_t m_MaxNumberOfFriends = 50; }; diff --git a/dCommon/BrickByBrickFix.cpp b/dCommon/BrickByBrickFix.cpp index d9b589c1..f8a9e022 100644 --- a/dCommon/BrickByBrickFix.cpp +++ b/dCommon/BrickByBrickFix.cpp @@ -13,9 +13,8 @@ //! Forward declarations -std::unique_ptr GetModelsFromDatabase(); void WriteSd0Magic(char* input, uint32_t chunkSize); -bool CheckSd0Magic(sql::Blob* streamToCheck); +bool CheckSd0Magic(std::istream& streamToCheck); /** * @brief Truncates all models with broken data from the database. @@ -24,28 +23,24 @@ bool CheckSd0Magic(sql::Blob* streamToCheck); */ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() { uint32_t modelsTruncated{}; - auto modelsToTruncate = GetModelsFromDatabase(); - bool previousCommitValue = Database::GetAutoCommit(); - Database::SetAutoCommit(false); - while (modelsToTruncate->next()) { - std::unique_ptr ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;")); - std::unique_ptr pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;")); + auto modelsToTruncate = Database::Get()->GetAllUgcModels(); + bool previousCommitValue = Database::Get()->GetAutoCommit(); + Database::Get()->SetAutoCommit(false); + for (auto& model : modelsToTruncate) { std::string completeUncompressedModel{}; uint32_t chunkCount{}; - uint64_t modelId = modelsToTruncate->getInt(1); - std::unique_ptr modelAsSd0(modelsToTruncate->getBlob(2)); // Check that header is sd0 by checking for the sd0 magic. - if (CheckSd0Magic(modelAsSd0.get())) { + if (CheckSd0Magic(model.lxfmlData)) { while (true) { uint32_t chunkSize{}; - modelAsSd0->read(reinterpret_cast(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream + model.lxfmlData.read(reinterpret_cast(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream // Check if good here since if at the end of an sd0 file, this will have eof flagged. - if (!modelAsSd0->good()) break; + if (!model.lxfmlData.good()) break; std::unique_ptr compressedChunk(new uint8_t[chunkSize]); for (uint32_t i = 0; i < chunkSize; i++) { - compressedChunk[i] = modelAsSd0->get(); + compressedChunk[i] = model.lxfmlData.get(); } // Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size. @@ -59,7 +54,7 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() { completeUncompressedModel.append((char*)uncompressedChunk.get()); completeUncompressedModel.resize(previousSize + actualUncompressedSize); } else { - LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, modelId, err); + LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err); break; } chunkCount++; @@ -75,26 +70,20 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() { "", completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos ) { - LOG("Brick-by-brick model %llu will be deleted!", modelId); - ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); - pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); - ugcModelToDelete->execute(); - pcModelToDelete->execute(); + LOG("Brick-by-brick model %llu will be deleted!", model.id); + Database::Get()->DeleteUgcModelData(model.id); modelsTruncated++; } } } else { - LOG("Brick-by-brick model %llu will be deleted!", modelId); - ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); - pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); - ugcModelToDelete->execute(); - pcModelToDelete->execute(); + LOG("Brick-by-brick model %llu will be deleted!", model.id); + Database::Get()->DeleteUgcModelData(model.id); modelsTruncated++; } } - Database::Commit(); - Database::SetAutoCommit(previousCommitValue); + Database::Get()->Commit(); + Database::Get()->SetAutoCommit(previousCommitValue); return modelsTruncated; } @@ -106,21 +95,17 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() { */ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() { uint32_t updatedModels = 0; - auto modelsToUpdate = GetModelsFromDatabase(); - auto previousAutoCommitState = Database::GetAutoCommit(); - Database::SetAutoCommit(false); - std::unique_ptr insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;")); - while (modelsToUpdate->next()) { - int64_t modelId = modelsToUpdate->getInt64(1); - std::unique_ptr oldLxfml(modelsToUpdate->getBlob(2)); + auto modelsToUpdate = Database::Get()->GetAllUgcModels(); + auto previousAutoCommitState = Database::Get()->GetAutoCommit(); + Database::Get()->SetAutoCommit(false); + for (auto& model : modelsToUpdate) { // Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib) // If it does, convert it to sd0. - if (oldLxfml->get() == 0x78 && oldLxfml->get() == 0xDA) { - + if (model.lxfmlData.get() == 0x78 && model.lxfmlData.get() == 0xDA) { // Get and save size of zlib compressed chunk. - oldLxfml->seekg(0, std::ios::end); - uint32_t oldLxfmlSize = static_cast(oldLxfml->tellg()); - oldLxfml->seekg(0); + model.lxfmlData.seekg(0, std::ios::end); + uint32_t oldLxfmlSize = static_cast(model.lxfmlData.tellg()); + model.lxfmlData.seekg(0); // Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size. uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9; @@ -128,34 +113,27 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() { WriteSd0Magic(sd0ConvertedModel.get(), oldLxfmlSize); for (uint32_t i = 9; i < oldLxfmlSizeWithHeader; i++) { - sd0ConvertedModel.get()[i] = oldLxfml->get(); + sd0ConvertedModel.get()[i] = model.lxfmlData.get(); } std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader); std::istringstream outputStringStream(outputString); - insertionStatement->setBlob(1, static_cast(&outputStringStream)); - insertionStatement->setInt64(2, modelId); try { - insertionStatement->executeUpdate(); - LOG("Updated model %i to sd0", modelId); + Database::Get()->UpdateUgcModelData(model.id, outputStringStream); + LOG("Updated model %i to sd0", model.id); updatedModels++; } catch (sql::SQLException exception) { LOG("Failed to update model %i. This model should be inspected manually to see why." - "The database error is %s", modelId, exception.what()); + "The database error is %s", model.id, exception.what()); } } } - Database::Commit(); - Database::SetAutoCommit(previousAutoCommitState); + Database::Get()->Commit(); + Database::Get()->SetAutoCommit(previousAutoCommitState); return updatedModels; } -std::unique_ptr GetModelsFromDatabase() { - std::unique_ptr modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;")); - return std::unique_ptr(modelsRawDataQuery->executeQuery()); -} - /** * @brief Writes sd0 magic at the front of a char* * @@ -171,6 +149,6 @@ void WriteSd0Magic(char* input, uint32_t chunkSize) { *reinterpret_cast(input + 5) = chunkSize; // Write the integer to the character array } -bool CheckSd0Magic(sql::Blob* streamToCheck) { - return streamToCheck->get() == 's' && streamToCheck->get() == 'd' && streamToCheck->get() == '0' && streamToCheck->get() == 0x01 && streamToCheck->get() == 0xFF; +bool CheckSd0Magic(std::istream& streamToCheck) { + return streamToCheck.get() == 's' && streamToCheck.get() == 'd' && streamToCheck.get() == '0' && streamToCheck.get() == 0x01 && streamToCheck.get() == 0xFF; } diff --git a/dDatabase/CDClientDatabase.cpp b/dDatabase/CDClientDatabase/CDClientDatabase.cpp similarity index 100% rename from dDatabase/CDClientDatabase.cpp rename to dDatabase/CDClientDatabase/CDClientDatabase.cpp diff --git a/dDatabase/CDClientDatabase.h b/dDatabase/CDClientDatabase/CDClientDatabase.h similarity index 100% rename from dDatabase/CDClientDatabase.h rename to dDatabase/CDClientDatabase/CDClientDatabase.h diff --git a/dDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp similarity index 100% rename from dDatabase/CDClientManager.cpp rename to dDatabase/CDClientDatabase/CDClientManager.cpp diff --git a/dDatabase/CDClientManager.h b/dDatabase/CDClientDatabase/CDClientManager.h similarity index 100% rename from dDatabase/CDClientManager.h rename to dDatabase/CDClientDatabase/CDClientManager.h diff --git a/dDatabase/Tables/CDActivitiesTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp similarity index 100% rename from dDatabase/Tables/CDActivitiesTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp diff --git a/dDatabase/Tables/CDActivitiesTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h similarity index 100% rename from dDatabase/Tables/CDActivitiesTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h diff --git a/dDatabase/Tables/CDActivityRewardsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDActivityRewardsTable.cpp similarity index 100% rename from dDatabase/Tables/CDActivityRewardsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDActivityRewardsTable.cpp diff --git a/dDatabase/Tables/CDActivityRewardsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDActivityRewardsTable.h similarity index 100% rename from dDatabase/Tables/CDActivityRewardsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDActivityRewardsTable.h diff --git a/dDatabase/Tables/CDAnimationsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDAnimationsTable.cpp similarity index 100% rename from dDatabase/Tables/CDAnimationsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDAnimationsTable.cpp diff --git a/dDatabase/Tables/CDAnimationsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDAnimationsTable.h similarity index 100% rename from dDatabase/Tables/CDAnimationsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDAnimationsTable.h diff --git a/dDatabase/Tables/CDBehaviorParameterTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDBehaviorParameterTable.cpp similarity index 100% rename from dDatabase/Tables/CDBehaviorParameterTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDBehaviorParameterTable.cpp diff --git a/dDatabase/Tables/CDBehaviorParameterTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDBehaviorParameterTable.h similarity index 100% rename from dDatabase/Tables/CDBehaviorParameterTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDBehaviorParameterTable.h diff --git a/dDatabase/Tables/CDBehaviorTemplateTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDBehaviorTemplateTable.cpp similarity index 100% rename from dDatabase/Tables/CDBehaviorTemplateTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDBehaviorTemplateTable.cpp diff --git a/dDatabase/Tables/CDBehaviorTemplateTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDBehaviorTemplateTable.h similarity index 100% rename from dDatabase/Tables/CDBehaviorTemplateTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDBehaviorTemplateTable.h diff --git a/dDatabase/Tables/CDBrickIDTableTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDBrickIDTableTable.cpp similarity index 100% rename from dDatabase/Tables/CDBrickIDTableTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDBrickIDTableTable.cpp diff --git a/dDatabase/Tables/CDBrickIDTableTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDBrickIDTableTable.h similarity index 100% rename from dDatabase/Tables/CDBrickIDTableTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDBrickIDTableTable.h diff --git a/dDatabase/Tables/CDComponentsRegistryTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDComponentsRegistryTable.cpp similarity index 100% rename from dDatabase/Tables/CDComponentsRegistryTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDComponentsRegistryTable.cpp diff --git a/dDatabase/Tables/CDComponentsRegistryTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDComponentsRegistryTable.h similarity index 96% rename from dDatabase/Tables/CDComponentsRegistryTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDComponentsRegistryTable.h index fc461989..fce4f6aa 100644 --- a/dDatabase/Tables/CDComponentsRegistryTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDComponentsRegistryTable.h @@ -3,6 +3,8 @@ // Custom Classes #include "CDTable.h" +#include + enum class eReplicaComponentType : uint32_t; struct CDComponentsRegistry { unsigned int id; //!< The LOT is used as the ID diff --git a/dDatabase/Tables/CDCurrencyTableTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDCurrencyTableTable.cpp similarity index 100% rename from dDatabase/Tables/CDCurrencyTableTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDCurrencyTableTable.cpp diff --git a/dDatabase/Tables/CDCurrencyTableTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDCurrencyTableTable.h similarity index 100% rename from dDatabase/Tables/CDCurrencyTableTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDCurrencyTableTable.h diff --git a/dDatabase/Tables/CDDestructibleComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDDestructibleComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDDestructibleComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDDestructibleComponentTable.cpp diff --git a/dDatabase/Tables/CDDestructibleComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDDestructibleComponentTable.h similarity index 100% rename from dDatabase/Tables/CDDestructibleComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDDestructibleComponentTable.h diff --git a/dDatabase/Tables/CDEmoteTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDEmoteTable.cpp similarity index 100% rename from dDatabase/Tables/CDEmoteTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDEmoteTable.cpp diff --git a/dDatabase/Tables/CDEmoteTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDEmoteTable.h similarity index 100% rename from dDatabase/Tables/CDEmoteTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDEmoteTable.h diff --git a/dDatabase/Tables/CDFeatureGatingTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDFeatureGatingTable.cpp similarity index 100% rename from dDatabase/Tables/CDFeatureGatingTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDFeatureGatingTable.cpp diff --git a/dDatabase/Tables/CDFeatureGatingTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDFeatureGatingTable.h similarity index 100% rename from dDatabase/Tables/CDFeatureGatingTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDFeatureGatingTable.h diff --git a/dDatabase/Tables/CDInventoryComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDInventoryComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDInventoryComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDInventoryComponentTable.cpp diff --git a/dDatabase/Tables/CDInventoryComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDInventoryComponentTable.h similarity index 100% rename from dDatabase/Tables/CDInventoryComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDInventoryComponentTable.h diff --git a/dDatabase/Tables/CDItemComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDItemComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp diff --git a/dDatabase/Tables/CDItemComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.h similarity index 100% rename from dDatabase/Tables/CDItemComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.h diff --git a/dDatabase/Tables/CDItemSetSkillsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDItemSetSkillsTable.cpp similarity index 100% rename from dDatabase/Tables/CDItemSetSkillsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDItemSetSkillsTable.cpp diff --git a/dDatabase/Tables/CDItemSetSkillsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDItemSetSkillsTable.h similarity index 100% rename from dDatabase/Tables/CDItemSetSkillsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDItemSetSkillsTable.h diff --git a/dDatabase/Tables/CDItemSetsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDItemSetsTable.cpp similarity index 100% rename from dDatabase/Tables/CDItemSetsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDItemSetsTable.cpp diff --git a/dDatabase/Tables/CDItemSetsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDItemSetsTable.h similarity index 100% rename from dDatabase/Tables/CDItemSetsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDItemSetsTable.h diff --git a/dDatabase/Tables/CDLevelProgressionLookupTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDLevelProgressionLookupTable.cpp similarity index 100% rename from dDatabase/Tables/CDLevelProgressionLookupTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDLevelProgressionLookupTable.cpp diff --git a/dDatabase/Tables/CDLevelProgressionLookupTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDLevelProgressionLookupTable.h similarity index 100% rename from dDatabase/Tables/CDLevelProgressionLookupTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDLevelProgressionLookupTable.h diff --git a/dDatabase/Tables/CDLootMatrixTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDLootMatrixTable.cpp similarity index 100% rename from dDatabase/Tables/CDLootMatrixTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDLootMatrixTable.cpp diff --git a/dDatabase/Tables/CDLootMatrixTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDLootMatrixTable.h similarity index 100% rename from dDatabase/Tables/CDLootMatrixTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDLootMatrixTable.h diff --git a/dDatabase/Tables/CDLootTableTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDLootTableTable.cpp similarity index 100% rename from dDatabase/Tables/CDLootTableTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDLootTableTable.cpp diff --git a/dDatabase/Tables/CDLootTableTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDLootTableTable.h similarity index 100% rename from dDatabase/Tables/CDLootTableTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDLootTableTable.h diff --git a/dDatabase/Tables/CDMissionEmailTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionEmailTable.cpp similarity index 100% rename from dDatabase/Tables/CDMissionEmailTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionEmailTable.cpp diff --git a/dDatabase/Tables/CDMissionEmailTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionEmailTable.h similarity index 100% rename from dDatabase/Tables/CDMissionEmailTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionEmailTable.h diff --git a/dDatabase/Tables/CDMissionNPCComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionNPCComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDMissionNPCComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionNPCComponentTable.cpp diff --git a/dDatabase/Tables/CDMissionNPCComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionNPCComponentTable.h similarity index 100% rename from dDatabase/Tables/CDMissionNPCComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionNPCComponentTable.h diff --git a/dDatabase/Tables/CDMissionTasksTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionTasksTable.cpp similarity index 100% rename from dDatabase/Tables/CDMissionTasksTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionTasksTable.cpp diff --git a/dDatabase/Tables/CDMissionTasksTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionTasksTable.h similarity index 100% rename from dDatabase/Tables/CDMissionTasksTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionTasksTable.h diff --git a/dDatabase/Tables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp similarity index 100% rename from dDatabase/Tables/CDMissionsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp diff --git a/dDatabase/Tables/CDMissionsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h similarity index 100% rename from dDatabase/Tables/CDMissionsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h diff --git a/dDatabase/Tables/CDMovementAIComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMovementAIComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDMovementAIComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDMovementAIComponentTable.cpp diff --git a/dDatabase/Tables/CDMovementAIComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMovementAIComponentTable.h similarity index 100% rename from dDatabase/Tables/CDMovementAIComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDMovementAIComponentTable.h diff --git a/dDatabase/Tables/CDObjectSkillsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp similarity index 100% rename from dDatabase/Tables/CDObjectSkillsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp diff --git a/dDatabase/Tables/CDObjectSkillsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h similarity index 100% rename from dDatabase/Tables/CDObjectSkillsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h diff --git a/dDatabase/Tables/CDObjectsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp similarity index 100% rename from dDatabase/Tables/CDObjectsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp diff --git a/dDatabase/Tables/CDObjectsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.h similarity index 100% rename from dDatabase/Tables/CDObjectsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.h diff --git a/dDatabase/Tables/CDPackageComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPackageComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDPackageComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDPackageComponentTable.cpp diff --git a/dDatabase/Tables/CDPackageComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDPackageComponentTable.h similarity index 100% rename from dDatabase/Tables/CDPackageComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDPackageComponentTable.h diff --git a/dDatabase/Tables/CDPhysicsComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPhysicsComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDPhysicsComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDPhysicsComponentTable.cpp diff --git a/dDatabase/Tables/CDPhysicsComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDPhysicsComponentTable.h similarity index 100% rename from dDatabase/Tables/CDPhysicsComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDPhysicsComponentTable.h diff --git a/dDatabase/Tables/CDPropertyEntranceComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPropertyEntranceComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDPropertyEntranceComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDPropertyEntranceComponentTable.cpp diff --git a/dDatabase/Tables/CDPropertyEntranceComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDPropertyEntranceComponentTable.h similarity index 100% rename from dDatabase/Tables/CDPropertyEntranceComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDPropertyEntranceComponentTable.h diff --git a/dDatabase/Tables/CDPropertyTemplateTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPropertyTemplateTable.cpp similarity index 100% rename from dDatabase/Tables/CDPropertyTemplateTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDPropertyTemplateTable.cpp diff --git a/dDatabase/Tables/CDPropertyTemplateTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDPropertyTemplateTable.h similarity index 100% rename from dDatabase/Tables/CDPropertyTemplateTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDPropertyTemplateTable.h diff --git a/dDatabase/Tables/CDProximityMonitorComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDProximityMonitorComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDProximityMonitorComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDProximityMonitorComponentTable.cpp diff --git a/dDatabase/Tables/CDProximityMonitorComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDProximityMonitorComponentTable.h similarity index 100% rename from dDatabase/Tables/CDProximityMonitorComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDProximityMonitorComponentTable.h diff --git a/dDatabase/Tables/CDRailActivatorComponent.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp similarity index 100% rename from dDatabase/Tables/CDRailActivatorComponent.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp diff --git a/dDatabase/Tables/CDRailActivatorComponent.h b/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.h similarity index 100% rename from dDatabase/Tables/CDRailActivatorComponent.h rename to dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.h diff --git a/dDatabase/Tables/CDRarityTableTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRarityTableTable.cpp similarity index 100% rename from dDatabase/Tables/CDRarityTableTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDRarityTableTable.cpp diff --git a/dDatabase/Tables/CDRarityTableTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDRarityTableTable.h similarity index 100% rename from dDatabase/Tables/CDRarityTableTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDRarityTableTable.h diff --git a/dDatabase/Tables/CDRebuildComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRebuildComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDRebuildComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDRebuildComponentTable.cpp diff --git a/dDatabase/Tables/CDRebuildComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDRebuildComponentTable.h similarity index 100% rename from dDatabase/Tables/CDRebuildComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDRebuildComponentTable.h diff --git a/dDatabase/Tables/CDRewardsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRewardsTable.cpp similarity index 100% rename from dDatabase/Tables/CDRewardsTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDRewardsTable.cpp diff --git a/dDatabase/Tables/CDRewardsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDRewardsTable.h similarity index 100% rename from dDatabase/Tables/CDRewardsTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDRewardsTable.h diff --git a/dDatabase/Tables/CDScriptComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDScriptComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDScriptComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDScriptComponentTable.cpp diff --git a/dDatabase/Tables/CDScriptComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDScriptComponentTable.h similarity index 100% rename from dDatabase/Tables/CDScriptComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDScriptComponentTable.h diff --git a/dDatabase/Tables/CDSkillBehaviorTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDSkillBehaviorTable.cpp similarity index 100% rename from dDatabase/Tables/CDSkillBehaviorTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDSkillBehaviorTable.cpp diff --git a/dDatabase/Tables/CDSkillBehaviorTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDSkillBehaviorTable.h similarity index 100% rename from dDatabase/Tables/CDSkillBehaviorTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDSkillBehaviorTable.h diff --git a/dDatabase/Tables/CDTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDTable.h similarity index 100% rename from dDatabase/Tables/CDTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDTable.h diff --git a/dDatabase/Tables/CDVendorComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDVendorComponentTable.cpp similarity index 100% rename from dDatabase/Tables/CDVendorComponentTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDVendorComponentTable.cpp diff --git a/dDatabase/Tables/CDVendorComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDVendorComponentTable.h similarity index 100% rename from dDatabase/Tables/CDVendorComponentTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDVendorComponentTable.h diff --git a/dDatabase/Tables/CDZoneTableTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDZoneTableTable.cpp similarity index 100% rename from dDatabase/Tables/CDZoneTableTable.cpp rename to dDatabase/CDClientDatabase/CDClientTables/CDZoneTableTable.cpp diff --git a/dDatabase/Tables/CDZoneTableTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDZoneTableTable.h similarity index 100% rename from dDatabase/Tables/CDZoneTableTable.h rename to dDatabase/CDClientDatabase/CDClientTables/CDZoneTableTable.h diff --git a/dDatabase/Tables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt similarity index 93% rename from dDatabase/Tables/CMakeLists.txt rename to dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index b6a02b02..43ff52b2 100644 --- a/dDatabase/Tables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -1,4 +1,4 @@ -set(DDATABASE_TABLES_SOURCES "CDActivitiesTable.cpp" +set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDActivityRewardsTable.cpp" "CDAnimationsTable.cpp" "CDBehaviorParameterTable.cpp" diff --git a/dDatabase/CDClientDatabase/CMakeLists.txt b/dDatabase/CDClientDatabase/CMakeLists.txt new file mode 100644 index 00000000..2645c215 --- /dev/null +++ b/dDatabase/CDClientDatabase/CMakeLists.txt @@ -0,0 +1,12 @@ +set(DDATABASE_CDCLIENTDATABASE_SOURCES + "CDClientDatabase.cpp" + "CDClientManager.cpp" +) + +add_subdirectory(CDClientTables) + +foreach(file ${DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES}) + set(DDATABASE_CDCLIENTDATABASE_SOURCES ${DDATABASE_CDCLIENTDATABASE_SOURCES} "CDClientTables/${file}") +endforeach() + +set(DDATABASE_CDCLIENTDATABASE_SOURCES ${DDATABASE_CDCLIENTDATABASE_SOURCES} PARENT_SCOPE) diff --git a/dDatabase/CMakeLists.txt b/dDatabase/CMakeLists.txt index 9e55fbe2..20f37bec 100644 --- a/dDatabase/CMakeLists.txt +++ b/dDatabase/CMakeLists.txt @@ -1,12 +1,15 @@ -set(DDATABASE_SOURCES "CDClientDatabase.cpp" - "CDClientManager.cpp" - "Database.cpp" - "MigrationRunner.cpp") +set(DDATABASE_SOURCES) -add_subdirectory(Tables) +add_subdirectory(CDClientDatabase) -foreach(file ${DDATABASE_TABLES_SOURCES}) - set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Tables/${file}") +foreach(file ${DDATABASE_CDCLIENTDATABASE_SOURCES}) + set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "CDClientDatabase/${file}") +endforeach() + +add_subdirectory(GameDatabase) + +foreach(file ${DDATABASE_GAMEDATABASE_SOURCES}) + set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "GameDatabase/${file}") endforeach() add_library(dDatabase STATIC ${DDATABASE_SOURCES}) diff --git a/dDatabase/Database.cpp b/dDatabase/Database.cpp deleted file mode 100644 index 5fc76786..00000000 --- a/dDatabase/Database.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "Database.h" -#include "Game.h" -#include "dConfig.h" -#include "Logger.h" -using namespace std; - -#pragma warning (disable:4251) //Disables SQL warnings - -sql::Driver* Database::driver; -sql::Connection* Database::con; -sql::Properties Database::props; -std::string Database::database; - -void Database::Connect(const string& host, const string& database, const string& username, const string& password) { - - //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 != "") LOG("Destroying MySQL connection from %s!", source.c_str()); - else LOG("Destroying MySQL connection!"); - } - - 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(); - LOG("Trying to reconnect to MySQL"); - } - - if (!con->isValid() || con->isClosed()) { - delete con; - - con = nullptr; - - Connect(); - LOG("Trying to reconnect to MySQL from invalid or closed connection"); - } - - auto* stmt = con->prepareStatement(str); - - return stmt; -} //CreatePreppedStmt - -void Database::Commit() { - Database::con->commit(); -} - -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); -} diff --git a/dDatabase/Database.h b/dDatabase/Database.h deleted file mode 100644 index f4d13da3..00000000 --- a/dDatabase/Database.h +++ /dev/null @@ -1,31 +0,0 @@ -#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()) {} -}; - -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 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; } -}; diff --git a/dDatabase/GameDatabase/CMakeLists.txt b/dDatabase/GameDatabase/CMakeLists.txt new file mode 100644 index 00000000..c32007bb --- /dev/null +++ b/dDatabase/GameDatabase/CMakeLists.txt @@ -0,0 +1,12 @@ +set(DDATABASE_GAMEDATABASE_SOURCES + "Database.cpp" + "MigrationRunner.cpp" +) + +add_subdirectory(MySQL) + +foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES}) + set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}") +endforeach() + +set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} PARENT_SCOPE) diff --git a/dDatabase/GameDatabase/Database.cpp b/dDatabase/GameDatabase/Database.cpp new file mode 100644 index 00000000..cb4f989a --- /dev/null +++ b/dDatabase/GameDatabase/Database.cpp @@ -0,0 +1,40 @@ +#include "Database.h" +#include "Game.h" +#include "dConfig.h" +#include "Logger.h" +#include "MySQLDatabase.h" +#include "DluAssert.h" + +#pragma warning (disable:4251) //Disables SQL warnings + +namespace { + GameDatabase* database = nullptr; +} + +void Database::Connect() { + if (database) { + LOG("Tried to connect to database when it's already connected!"); + return; + } + + database = new MySQLDatabase(); + database->Connect(); +} + +GameDatabase* Database::Get() { + if (!database) { + LOG("Tried to get database when it's not connected!"); + Connect(); + } + return database; +} + +void Database::Destroy(std::string source) { + if (database) { + database->Destroy(source); + delete database; + database = nullptr; + } else { + LOG("Trying to destroy database when it's not connected!"); + } +} diff --git a/dDatabase/GameDatabase/Database.h b/dDatabase/GameDatabase/Database.h new file mode 100644 index 00000000..3eb292d1 --- /dev/null +++ b/dDatabase/GameDatabase/Database.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include "GameDatabase.h" + +namespace Database { + void Connect(); + GameDatabase* Get(); + void Destroy(std::string source = ""); +}; diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h new file mode 100644 index 00000000..7d8c7de9 --- /dev/null +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -0,0 +1,55 @@ +#ifndef __GAMEDATABASE__H__ +#define __GAMEDATABASE__H__ + +#include + +#include "ILeaderboard.h" +#include "IPlayerCheatDetections.h" +#include "ICommandLog.h" +#include "IMail.h" +#include "IObjectIdTracker.h" +#include "IPlayKeys.h" +#include "IServers.h" +#include "IBugReports.h" +#include "IPropertyContents.h" +#include "IProperty.h" +#include "IPetNames.h" +#include "ICharXml.h" +#include "IMigrationHistory.h" +#include "IUgc.h" +#include "IFriends.h" +#include "ICharInfo.h" +#include "IAccounts.h" +#include "IActivityLog.h" + +namespace sql { + class Statement; + class PreparedStatement; +}; + +#ifdef _DEBUG +# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0) +#else +# define DLU_SQL_TRY_CATCH_RETHROW(x) x +#endif // _DEBUG + +class GameDatabase : + public IPlayKeys, public ILeaderboard, public IObjectIdTracker, public IServers, + public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, + public IPropertyContents, public IProperty, public IPetNames, public ICharXml, + public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, + public IAccounts, public IActivityLog { +public: + virtual ~GameDatabase() = default; + // TODO: These should be made private. + virtual void Connect() = 0; + virtual void Destroy(std::string source = "") = 0; + virtual void ExecuteCustomQuery(const std::string_view query) = 0; + virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0; + virtual void Commit() = 0; + virtual bool GetAutoCommit() = 0; + virtual void SetAutoCommit(bool value) = 0; + virtual void DeleteCharacter(const uint32_t characterId) = 0; +}; + +#endif //!__GAMEDATABASE__H__ diff --git a/dDatabase/GameDatabase/ITables/IAccounts.h b/dDatabase/GameDatabase/ITables/IAccounts.h new file mode 100644 index 00000000..1b1f85a7 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IAccounts.h @@ -0,0 +1,37 @@ +#ifndef __IACCOUNTS__H__ +#define __IACCOUNTS__H__ + +#include +#include +#include + +enum class eGameMasterLevel : uint8_t; + +class IAccounts { +public: + struct Info { + std::string bcryptPassword; + uint32_t id{}; + uint32_t playKeyId{}; + bool banned{}; + bool locked{}; + eGameMasterLevel maxGmLevel{}; + }; + + // Get the account info for the given username. + virtual std::optional GetAccountInfo(const std::string_view username) = 0; + + // Update the account's unmute time. + virtual void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) = 0; + + // Update the account's ban status. + virtual void UpdateAccountBan(const uint32_t accountId, const bool banned) = 0; + + // Update the account's password. + virtual void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) = 0; + + // Add a new account to the database. + virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0; +}; + +#endif //!__IACCOUNTS__H__ diff --git a/dDatabase/GameDatabase/ITables/IActivityLog.h b/dDatabase/GameDatabase/ITables/IActivityLog.h new file mode 100644 index 00000000..a67b61a4 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IActivityLog.h @@ -0,0 +1,19 @@ +#ifndef __IACTIVITYLOG__H__ +#define __IACTIVITYLOG__H__ + +#include + +#include "dCommonVars.h" + +enum class eActivityType : uint32_t { + PlayerLoggedIn, + PlayerLoggedOut, +}; + +class IActivityLog { +public: + // Update the activity log for the given account. + virtual void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) = 0; +}; + +#endif //!__IACTIVITYLOG__H__ diff --git a/dDatabase/GameDatabase/ITables/IBugReports.h b/dDatabase/GameDatabase/ITables/IBugReports.h new file mode 100644 index 00000000..29a6180f --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IBugReports.h @@ -0,0 +1,20 @@ +#ifndef __IBUGREPORTS__H__ +#define __IBUGREPORTS__H__ + +#include +#include + +class IBugReports { +public: + struct Info { + std::string body; + std::string clientVersion; + std::string otherPlayer; + std::string selection; + uint32_t characterId{}; + }; + + // Add a new bug report to the database. + virtual void InsertNewBugReport(const Info& info) = 0; +}; +#endif //!__IBUGREPORTS__H__ diff --git a/dDatabase/GameDatabase/ITables/ICharInfo.h b/dDatabase/GameDatabase/ITables/ICharInfo.h new file mode 100644 index 00000000..416dad2c --- /dev/null +++ b/dDatabase/GameDatabase/ITables/ICharInfo.h @@ -0,0 +1,49 @@ +#ifndef __ICHARINFO__H__ +#define __ICHARINFO__H__ + +#include +#include +#include +#include +#include + +#include "ePermissionMap.h" + +class ICharInfo { +public: + struct Info { + std::string name; + std::string pendingName; + uint32_t id{}; + uint32_t accountId{}; + bool needsRename{}; + LWOCLONEID cloneId{}; + ePermissionMap permissionMap{}; + }; + + // Get the approved names of all characters. + virtual std::vector GetApprovedCharacterNames() = 0; + + // Get the character info for the given character id. + virtual std::optional GetCharacterInfo(const uint32_t charId) = 0; + + // Get the character info for the given character name. + virtual std::optional GetCharacterInfo(const std::string_view name) = 0; + + // Get the character ids for the given account. + virtual std::vector GetAccountCharacterIds(const uint32_t accountId) = 0; + + // Insert a new character into the database. + virtual void InsertNewCharacter(const ICharInfo::Info info) = 0; + + // Set the name of the given character. + virtual void SetCharacterName(const uint32_t characterId, const std::string_view name) = 0; + + // Set the pending name of the given character. + virtual void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) = 0; + + // Updates the given character ids last login to be right now. + virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0; +}; + +#endif //!__ICHARINFO__H__ diff --git a/dDatabase/GameDatabase/ITables/ICharXml.h b/dDatabase/GameDatabase/ITables/ICharXml.h new file mode 100644 index 00000000..c7ada075 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/ICharXml.h @@ -0,0 +1,20 @@ +#ifndef __ICHARXML__H__ +#define __ICHARXML__H__ + +#include +#include +#include + +class ICharXml { +public: + // Get the character xml for the given character id. + virtual std::string GetCharacterXml(const uint32_t charId) = 0; + + // Update the character xml for the given character id. + virtual void UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) = 0; + + // Insert the character xml for the given character id. + virtual void InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) = 0; +}; + +#endif //!__ICHARXML__H__ diff --git a/dDatabase/GameDatabase/ITables/ICommandLog.h b/dDatabase/GameDatabase/ITables/ICommandLog.h new file mode 100644 index 00000000..63595360 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/ICommandLog.h @@ -0,0 +1,14 @@ +#ifndef __ICOMMANDLOG__H__ +#define __ICOMMANDLOG__H__ + +#include +#include + +class ICommandLog { +public: + + // Insert a new slash command log entry. + virtual void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) = 0; +}; + +#endif //!__ICOMMANDLOG__H__ diff --git a/dDatabase/GameDatabase/ITables/IFriends.h b/dDatabase/GameDatabase/ITables/IFriends.h new file mode 100644 index 00000000..6f96f441 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IFriends.h @@ -0,0 +1,32 @@ +#ifndef __IFRIENDS__H__ +#define __IFRIENDS__H__ + +#include +#include +#include + +class IFriends { +public: + struct BestFriendStatus { + uint32_t playerCharacterId{}; + uint32_t friendCharacterId{}; + uint32_t bestFriendStatus{}; + }; + + // Get the friends list for the given character id. + virtual std::vector GetFriendsList(const uint32_t charId) = 0; + + // Get the best friend status for the given player and friend character ids. + virtual std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; + + // Set the best friend status for the given player and friend character ids. + virtual void SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) = 0; + + // Add a friend to the given character id. + virtual void AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; + + // Remove a friend from the given character id. + virtual void RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; +}; + +#endif //!__IFRIENDS__H__ diff --git a/dDatabase/GameDatabase/ITables/ILeaderboard.h b/dDatabase/GameDatabase/ITables/ILeaderboard.h new file mode 100644 index 00000000..84d44eb2 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/ILeaderboard.h @@ -0,0 +1,14 @@ +#ifndef __ILEADERBOARD__H__ +#define __ILEADERBOARD__H__ + +#include +#include + +class ILeaderboard { +public: + + // Get the donation total for the given activity id. + virtual std::optional GetDonationTotal(const uint32_t activityId) = 0; +}; + +#endif //!__ILEADERBOARD__H__ diff --git a/dDatabase/GameDatabase/ITables/IMail.h b/dDatabase/GameDatabase/ITables/IMail.h new file mode 100644 index 00000000..7fbc8230 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IMail.h @@ -0,0 +1,54 @@ +#ifndef __IMAIL__H__ +#define __IMAIL__H__ + +#include +#include +#include + +#include "dCommonVars.h" +#include "NiQuaternion.h" +#include "NiPoint3.h" + +class IMail { +public: + struct MailInfo { + std::string senderUsername; + std::string recipient; + std::string subject; + std::string body; + uint64_t id{}; + uint32_t senderId{}; + uint32_t receiverId{}; + uint64_t timeSent{}; + bool wasRead{}; + struct { + LWOOBJID itemID{}; + int32_t itemCount{}; + LOT itemLOT{}; + LWOOBJID itemSubkey{}; + }; + }; + + // Insert a new mail into the database. + virtual void InsertNewMail(const MailInfo& mail) = 0; + + // Get the mail for the given character id. + virtual std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) = 0; + + // Get the mail for the given mail id. + virtual std::optional GetMail(const uint64_t mailId) = 0; + + // Get the number of unread mail for the given character id. + virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0; + + // Mark the given mail as read. + virtual void MarkMailRead(const uint64_t mailId) = 0; + + // Claim the item from the given mail. + virtual void ClaimMailItem(const uint64_t mailId) = 0; + + // Delete the given mail. + virtual void DeleteMail(const uint64_t mailId) = 0; +}; + +#endif //!__IMAIL__H__ diff --git a/dDatabase/GameDatabase/ITables/IMigrationHistory.h b/dDatabase/GameDatabase/ITables/IMigrationHistory.h new file mode 100644 index 00000000..21f27b4a --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IMigrationHistory.h @@ -0,0 +1,17 @@ +#ifndef __IMIGRATIONHISTORY__H__ +#define __IMIGRATIONHISTORY__H__ + +#include + +class IMigrationHistory { +public: + // Create the migration history table. + virtual void CreateMigrationHistoryTable() = 0; + + // Check if the given migration has been run. + virtual bool IsMigrationRun(const std::string_view str) = 0; + + // Insert the given migration into the migration history table. + virtual void InsertMigration(const std::string_view str) = 0; +}; +#endif //!__IMIGRATIONHISTORY__H__ diff --git a/dDatabase/GameDatabase/ITables/IObjectIdTracker.h b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h new file mode 100644 index 00000000..cbe34b6d --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h @@ -0,0 +1,19 @@ +#ifndef __IOBJECTIDTRACKER__H__ +#define __IOBJECTIDTRACKER__H__ + +#include +#include + +class IObjectIdTracker { +public: + // Get the current persistent id. + virtual std::optional GetCurrentPersistentId() = 0; + + // Insert the default persistent id. + virtual void InsertDefaultPersistentId() = 0; + + // Update the persistent id. + virtual void UpdatePersistentId(const uint32_t newId) = 0; +}; + +#endif //!__IOBJECTIDTRACKER__H__ diff --git a/dDatabase/GameDatabase/ITables/IPetNames.h b/dDatabase/GameDatabase/ITables/IPetNames.h new file mode 100644 index 00000000..e82f4905 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IPetNames.h @@ -0,0 +1,21 @@ +#ifndef __IPETNAMES__H__ +#define __IPETNAMES__H__ + +#include +#include + +class IPetNames { +public: + struct Info { + std::string petName; + int32_t approvalStatus{}; + }; + + // Set the pet name moderation status for the given pet id. + virtual void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) = 0; + + // Get pet info for the given pet id. + virtual std::optional GetPetNameInfo(const LWOOBJID& petId) = 0; +}; + +#endif //!__IPETNAMES__H__ diff --git a/dDatabase/GameDatabase/ITables/IPlayKeys.h b/dDatabase/GameDatabase/ITables/IPlayKeys.h new file mode 100644 index 00000000..0a1db389 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IPlayKeys.h @@ -0,0 +1,15 @@ +#ifndef __IPLAYKEYS__H__ +#define __IPLAYKEYS__H__ + +#include +#include + +class IPlayKeys { +public: + // Get the playkey id for the given playkey. + // Optional of bool may seem pointless, however the optional indicates if the playkey exists + // and the bool indicates if the playkey is active. + virtual std::optional IsPlaykeyActive(const int32_t playkeyId) = 0; +}; + +#endif //!__IPLAYKEYS__H__ diff --git a/dDatabase/GameDatabase/ITables/IPlayerCheatDetections.h b/dDatabase/GameDatabase/ITables/IPlayerCheatDetections.h new file mode 100644 index 00000000..939f5118 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IPlayerCheatDetections.h @@ -0,0 +1,20 @@ +#ifndef __IPLAYERCHEATDETECTIONS__H__ +#define __IPLAYERCHEATDETECTIONS__H__ + +#include +#include + +class IPlayerCheatDetections { +public: + struct Info { + std::optional userId = std::nullopt; + std::string username; + std::string systemAddress; + std::string extraMessage; + }; + + // Insert a new cheat detection. + virtual void InsertCheatDetection(const IPlayerCheatDetections::Info& info) = 0; +}; + +#endif //!__IPLAYERCHEATDETECTIONS__H__ diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h new file mode 100644 index 00000000..54994b51 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -0,0 +1,38 @@ +#ifndef __IPROPERTY__H__ +#define __IPROPERTY__H__ + +#include +#include + +class IProperty { +public: + struct Info { + std::string name; + std::string description; + std::string rejectionReason; + LWOOBJID id{}; + uint32_t ownerId{}; + LWOCLONEID cloneId{}; + int32_t privacyOption{}; + uint32_t modApproved{}; + uint32_t lastUpdatedTime{}; + uint32_t claimedTime{}; + uint32_t reputation{}; + }; + + // Get the property info for the given property id. + virtual std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; + + // Update the property moderation info for the given property id. + virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0; + + // Update the property details for the given property id. + virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0; + + // Update the property performance cost for the given property id. + virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0; + + // Insert a new property into the database. + virtual void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) = 0; +}; +#endif //!__IPROPERTY__H__ diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h new file mode 100644 index 00000000..c862ca94 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -0,0 +1,40 @@ +#ifndef __IPROPERTIESCONTENTS__H__ +#define __IPROPERTIESCONTENTS__H__ + +#include +#include + +class IPropertyContents { +public: + struct Model { + inline bool operator==(const LWOOBJID& other) const noexcept { + return id == other; + } + + NiPoint3 position; + NiQuaternion rotation; + LWOOBJID id{}; + LOT lot{}; + uint32_t ugcId{}; + }; + + // Inserts a new UGC model into the database. + virtual void InsertNewUgcModel( + std::istringstream& sd0Data, + const uint32_t blueprintId, + const uint32_t accountId, + const uint32_t characterId) = 0; + + // Get the property models for the given property id. + virtual std::vector GetPropertyModels(const LWOOBJID& propertyId) = 0; + + // Insert a new property model into the database. + virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; + + // Update the model position and rotation for the given property id. + virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0; + + // Remove the model for the given property id. + virtual void RemoveModel(const LWOOBJID& modelId) = 0; +}; +#endif //!__IPROPERTIESCONTENTS__H__ diff --git a/dDatabase/GameDatabase/ITables/IServers.h b/dDatabase/GameDatabase/ITables/IServers.h new file mode 100644 index 00000000..ee74bbb8 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IServers.h @@ -0,0 +1,21 @@ +#ifndef __ISERVERS__H__ +#define __ISERVERS__H__ + +#include +#include + +class IServers { +public: + struct MasterInfo { + std::string ip; + uint32_t port{}; + }; + + // Set the master server ip and port. + virtual void SetMasterIp(const std::string_view ip, const uint32_t port) = 0; + + // Get the master server info. + virtual std::optional GetMasterInfo() = 0; +}; + +#endif //!__ISERVERS__H__ diff --git a/dDatabase/GameDatabase/ITables/IUgc.h b/dDatabase/GameDatabase/ITables/IUgc.h new file mode 100644 index 00000000..024636ac --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IUgc.h @@ -0,0 +1,32 @@ +#ifndef __IUGC__H__ +#define __IUGC__H__ + +#include +#include +#include +#include +#include + +class IUgc { +public: + struct Model { + std::stringstream lxfmlData; + LWOOBJID id{}; + }; + + // Gets all UGC models for the given property id. + virtual std::vector GetUgcModels(const LWOOBJID& propertyId) = 0; + + // Gets all Ugcs models. + virtual std::vector GetAllUgcModels() = 0; + + // Removes ugc models that are not referenced by any property. + virtual void RemoveUnreferencedUgcModels() = 0; + + // Deletes the ugc model for the given model id. + virtual void DeleteUgcModelData(const LWOOBJID& modelId) = 0; + + // Inserts a new UGC model into the database. + virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) = 0; +}; +#endif //!__IUGC__H__ diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/GameDatabase/MigrationRunner.cpp similarity index 73% rename from dDatabase/MigrationRunner.cpp rename to dDatabase/GameDatabase/MigrationRunner.cpp index 2dbe9101..6bc3954d 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/GameDatabase/MigrationRunner.cpp @@ -32,9 +32,7 @@ Migration LoadMigration(std::string path) { } void MigrationRunner::RunMigrations() { - auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());"); - stmt->execute(); - delete stmt; + Database::Get()->CreateMigrationHistoryTable(); sql::SQLString finalSQL = ""; bool runSd0Migrations = false; @@ -45,13 +43,7 @@ void MigrationRunner::RunMigrations() { continue; } - stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); - stmt->setString(1, migration.name.c_str()); - auto* res = stmt->executeQuery(); - bool doExit = res->next(); - delete res; - delete stmt; - if (doExit) continue; + if (Database::Get()->IsMigrationRun(migration.name)) continue; LOG("Running migration: %s", migration.name.c_str()); if (migration.name == "dlu/5_brick_model_sd0.sql") { @@ -60,10 +52,7 @@ void MigrationRunner::RunMigrations() { finalSQL.append(migration.data.c_str()); } - stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - stmt->setString(1, migration.name.c_str()); - stmt->execute(); - delete stmt; + Database::Get()->InsertMigration(migration.name); } if (finalSQL.empty() && !runSd0Migrations) { @@ -73,11 +62,10 @@ void MigrationRunner::RunMigrations() { if (!finalSQL.empty()) { auto migration = GeneralUtils::SplitString(static_cast(finalSQL), ';'); - std::unique_ptr simpleStatement(Database::CreateStmt()); for (auto& query : migration) { try { if (query.empty()) continue; - simpleStatement->execute(query.c_str()); + Database::Get()->ExecuteCustomQuery(query.c_str()); } catch (sql::SQLException& e) { LOG("Encountered error running migration: %s", e.what()); } @@ -98,9 +86,7 @@ void MigrationRunner::RunSQLiteMigrations() { cdstmt.execQuery().finalize(); cdstmt.finalize(); - auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());"); - stmt->execute(); - delete stmt; + Database::Get()->CreateMigrationHistoryTable(); for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { auto migration = LoadMigration("cdserver/" + entry); @@ -111,25 +97,15 @@ void MigrationRunner::RunSQLiteMigrations() { cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); cdstmt.bind((int32_t) 1, migration.name.c_str()); auto cdres = cdstmt.execQuery(); - bool doExit = !cdres.eof(); - cdres.finalize(); - cdstmt.finalize(); - if (doExit) continue; + if (!cdres.eof()) continue; // Check first if there is entry in the migration history table on the main database. - stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); - stmt->setString(1, migration.name.c_str()); - auto* res = stmt->executeQuery(); - doExit = res->next(); - delete res; - delete stmt; - if (doExit) { + if (Database::Get()->IsMigrationRun(migration.name)) { // Insert into cdclient database if there is an entry in the main database but not the cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - cdstmt.bind((int32_t) 1, migration.name.c_str()); - cdstmt.execQuery().finalize(); - cdstmt.finalize(); + cdstmt.bind(static_cast(1), migration.name.c_str()); + cdstmt.execQuery(); continue; } @@ -149,8 +125,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Insert into cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); cdstmt.bind((int32_t) 1, migration.name.c_str()); - cdstmt.execQuery().finalize(); - cdstmt.finalize(); + cdstmt.execQuery(); CDClientDatabase::ExecuteQuery("COMMIT;"); } diff --git a/dDatabase/MigrationRunner.h b/dDatabase/GameDatabase/MigrationRunner.h similarity index 100% rename from dDatabase/MigrationRunner.h rename to dDatabase/GameDatabase/MigrationRunner.h diff --git a/dDatabase/GameDatabase/MySQL/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/CMakeLists.txt new file mode 100644 index 00000000..9114445d --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/CMakeLists.txt @@ -0,0 +1,11 @@ +SET(DDATABSE_DATABSES_MYSQL_SOURCES + "MySQLDatabase.cpp" +) + +add_subdirectory(Tables) + +foreach(file ${DDATABASES_DATABASES_MYSQL_TABLES_SOURCES}) + set(DDATABSE_DATABSES_MYSQL_SOURCES ${DDATABSE_DATABSES_MYSQL_SOURCES} "Tables/${file}") +endforeach() + +set(DDATABSE_DATABSES_MYSQL_SOURCES ${DDATABSE_DATABSES_MYSQL_SOURCES} PARENT_SCOPE) diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp new file mode 100644 index 00000000..259c3866 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp @@ -0,0 +1,113 @@ +#include "MySQLDatabase.h" + +#include "Database.h" +#include "Game.h" +#include "dConfig.h" +#include "Logger.h" + +namespace { + std::string databaseName; + sql::Properties properties; + sql::Driver* driver = nullptr; + sql::Connection* con = nullptr; +}; + +void MySQLDatabase::Connect() { + driver = sql::mariadb::get_driver_instance(); + + // 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://"; + std::string mysql_host = Game::config->GetValue("mysql_host"); + if (mysql_host.find(UNIX_PROTO) == 0) { + properties["hostName"] = "unix://localhost"; + properties["localSocket"] = mysql_host.substr(UNIX_PROTO.length()).c_str(); + } else if (mysql_host.find(PIPE_PROTO) == 0) { + properties["hostName"] = "pipe://localhost"; + properties["pipe"] = mysql_host.substr(PIPE_PROTO.length()).c_str(); + } else { + properties["hostName"] = mysql_host.c_str(); + } + properties["user"] = Game::config->GetValue("mysql_username").c_str(); + properties["password"] = Game::config->GetValue("mysql_password").c_str(); + properties["autoReconnect"] = "true"; + + databaseName = Game::config->GetValue("mysql_database").c_str(); + + // `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 (properties.find("localSocket") != properties.end() || properties.find("pipe") != properties.end()) { + con = driver->connect(properties); + } else { + con = driver->connect(properties["hostName"].c_str(), properties["user"].c_str(), properties["password"].c_str()); + } + con->setSchema(databaseName.c_str()); +} + +void MySQLDatabase::Destroy(std::string source) { + if (!con) return; + + if (source.empty()) LOG("Destroying MySQL connection!"); + else LOG("Destroying MySQL connection from %s!", source.c_str()); + + con->close(); + delete con; + con = nullptr; +} + +void MySQLDatabase::ExecuteCustomQuery(const std::string_view query) { + std::unique_ptr(con->createStatement())->execute(query.data()); +} + +sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) { + if (!con) { + Connect(); + LOG("Trying to reconnect to MySQL"); + } + + if (!con->isValid() || con->isClosed()) { + delete con; + + con = nullptr; + + Connect(); + LOG("Trying to reconnect to MySQL from invalid or closed connection"); + } + + return con->prepareStatement(sql::SQLString(query.c_str(), query.length())); +} + +void MySQLDatabase::Commit() { + 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 con->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. + con->setAutoCommit(value); +} + +void MySQLDatabase::DeleteCharacter(const uint32_t characterId) { + ExecuteDelete("DELETE FROM charxml WHERE id=? LIMIT 1;", characterId); + ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId); + ExecuteDelete("DELETE FROM leaderboard WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId); + ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId); + ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId); + ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId); + ExecuteDelete("DELETE FROM charinfo WHERE id=? LIMIT 1;", characterId); +} diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h new file mode 100644 index 00000000..bed79bb7 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -0,0 +1,245 @@ +#ifndef __MYSQLDATABASE__H__ +#define __MYSQLDATABASE__H__ + +#include +#include + +#include "GameDatabase.h" + +typedef std::unique_ptr& UniquePreppedStmtRef; + +// Purposefully no definition for this to provide linker errors in the case someone tries to +// bind a parameter to a type that isn't defined. +template +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const ParamType param); + +// This is a function to set each parameter in a prepared statement. +// This is accomplished with a combination of parameter packing and Fold Expressions. +// The constexpr if statement is used to prevent the compiler from trying to call SetParam with 0 arguments. +template +void SetParams(UniquePreppedStmtRef stmt, Args&&... args) { + if constexpr (sizeof...(args) != 0) { + int i = 1; + (SetParam(stmt, i++, args), ...); + } +} + +class MySQLDatabase : public GameDatabase { +public: + void Connect() override; + void Destroy(std::string source = "") override; + + sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override; + void Commit() override; + bool GetAutoCommit() override; + void SetAutoCommit(bool value) override; + void ExecuteCustomQuery(const std::string_view query) override; + + // Overloaded queries + std::optional GetMasterInfo() override; + + std::vector GetApprovedCharacterNames() override; + + std::vector GetFriendsList(uint32_t charID) override; + + std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override; + void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override; + void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; + void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; + void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override; + void DeleteUgcModelData(const LWOOBJID& modelId) override; + void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override; + std::vector GetAllUgcModels() override; + void CreateMigrationHistoryTable() override; + bool IsMigrationRun(const std::string_view str) override; + void InsertMigration(const std::string_view str) override; + std::optional GetCharacterInfo(const uint32_t charId) override; + std::optional GetCharacterInfo(const std::string_view charId) override; + std::string GetCharacterXml(const uint32_t accountId) override; + void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override; + std::optional GetAccountInfo(const std::string_view username) override; + void InsertNewCharacter(const ICharInfo::Info info) override; + void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override; + std::vector GetAccountCharacterIds(uint32_t accountId) override; + void DeleteCharacter(const uint32_t characterId) override; + void SetCharacterName(const uint32_t characterId, const std::string_view name) override; + void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override; + void UpdateLastLoggedInCharacter(const uint32_t characterId) override; + void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; + std::optional GetPetNameInfo(const LWOOBJID& petId) override; + std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; + void UpdatePropertyModerationInfo(const IProperty::Info& info) override; + void UpdatePropertyDetails(const IProperty::Info& info) override; + void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override; + std::vector GetPropertyModels(const LWOOBJID& propertyId) override; + void RemoveUnreferencedUgcModels() override; + void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; + void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override; + void RemoveModel(const LWOOBJID& modelId) override; + void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; + void InsertNewBugReport(const IBugReports::Info& info) override; + void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override; + void InsertNewMail(const IMail::MailInfo& mail) override; + void InsertNewUgcModel( + std::istringstream& sd0Data, + const uint32_t blueprintId, + const uint32_t accountId, + const uint32_t characterId) override; + std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; + std::optional GetMail(const uint64_t mailId) override; + uint32_t GetUnreadMailCount(const uint32_t characterId) override; + void MarkMailRead(const uint64_t mailId) override; + void DeleteMail(const uint64_t mailId) override; + void ClaimMailItem(const uint64_t mailId) override; + void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override; + void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; + void UpdateAccountBan(const uint32_t accountId, const bool banned) override; + void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; + void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; + void SetMasterIp(const std::string_view ip, const uint32_t port) override; + std::optional GetCurrentPersistentId() override; + void InsertDefaultPersistentId() override; + void UpdatePersistentId(const uint32_t id) override; + std::optional GetDonationTotal(const uint32_t activityId) override; + std::optional IsPlaykeyActive(const int32_t playkeyId) override; + std::vector GetUgcModels(const LWOOBJID& propertyId) override; +private: + + // Generic query functions that can be used for any query. + // Return type may be different depending on the query, so it is up to the caller to check the return type. + // The first argument is the query string, and the rest are the parameters to bind to the query. + // The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope + template + inline std::unique_ptr ExecuteSelect(const std::string& query, Args&&... args) { + std::unique_ptr preppedStmt(CreatePreppedStmt(query)); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(return std::unique_ptr(preppedStmt->executeQuery())); + } + + template + inline void ExecuteDelete(const std::string& query, Args&&... args) { + std::unique_ptr preppedStmt(CreatePreppedStmt(query)); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(preppedStmt->execute()); + } + + template + inline int32_t ExecuteUpdate(const std::string& query, Args&&... args) { + std::unique_ptr preppedStmt(CreatePreppedStmt(query)); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt->executeUpdate()); + } + + template + inline bool ExecuteInsert(const std::string& query, Args&&... args) { + std::unique_ptr preppedStmt(CreatePreppedStmt(query)); + SetParams(preppedStmt, std::forward(args)...); + DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt->execute()); + } +}; + +// Below are each of the definitions of SetParam for each supported type. + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) { + // LOG("%s", param.data()); + stmt->setString(index, param.data()); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) { + // LOG("%s", param); + stmt->setString(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) { + // LOG("%s", param.c_str()); + stmt->setString(index, param.c_str()); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) { + // LOG("%u", param); + stmt->setByte(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) { + // LOG("%d", param); + stmt->setByte(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) { + // LOG("%u", param); + stmt->setShort(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) { + // LOG("%d", param); + stmt->setShort(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) { + // LOG("%u", param); + stmt->setUInt(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) { + // LOG("%d", param); + stmt->setInt(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) { + // LOG("%llu", param); + stmt->setInt64(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) { + // LOG("%llu", param); + stmt->setUInt64(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) { + // LOG("%f", param); + stmt->setFloat(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) { + // LOG("%f", param); + stmt->setDouble(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) { + // LOG("%d", param); + stmt->setBoolean(index, param); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) { + // LOG("Blob"); + // This is the one time you will ever see me use const_cast. + stmt->setBlob(index, const_cast(param)); +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { + if (param) { + // LOG("%d", param.value()); + stmt->setInt(index, param.value()); + } else { + // LOG("Null"); + stmt->setNull(index, sql::DataType::SQLNULL); + } +} + +#endif //!__MYSQLDATABASE__H__ diff --git a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp new file mode 100644 index 00000000..801f444d --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp @@ -0,0 +1,37 @@ +#include "MySQLDatabase.h" + +#include "eGameMasterLevel.h" + +std::optional MySQLDatabase::GetAccountInfo(const std::string_view username) { + auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level FROM accounts WHERE name = ? LIMIT 1;", username); + + if (!result->next()) { + return std::nullopt; + } + + IAccounts::Info toReturn; + toReturn.id = result->getUInt("id"); + toReturn.maxGmLevel = static_cast(result->getInt("gm_level")); + toReturn.bcryptPassword = result->getString("password").c_str(); + toReturn.banned = result->getBoolean("banned"); + toReturn.locked = result->getBoolean("locked"); + toReturn.playKeyId = result->getUInt("play_key_id"); + + return toReturn; +} + +void MySQLDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) { + ExecuteUpdate("UPDATE accounts SET mute_expire = ? WHERE id = ?;", timeToUnmute, accountId); +} + +void MySQLDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) { + ExecuteUpdate("UPDATE accounts SET banned = ? WHERE id = ?;", banned, accountId); +} + +void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) { + ExecuteUpdate("UPDATE accounts SET password = ? WHERE id = ?;", bcryptpassword, accountId); +} + +void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) { + ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast(eGameMasterLevel::OPERATOR)); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp b/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp new file mode 100644 index 00000000..50fd6b79 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp @@ -0,0 +1,6 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) { + ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);", + characterId, static_cast(activityType), static_cast(time(NULL)), mapId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/BugReports.cpp b/dDatabase/GameDatabase/MySQL/Tables/BugReports.cpp new file mode 100644 index 00000000..4f23941d --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/BugReports.cpp @@ -0,0 +1,6 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::InsertNewBugReport(const IBugReports::Info& info) { + ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)", + info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt new file mode 100644 index 00000000..e9593ba9 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt @@ -0,0 +1,22 @@ +set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES + "Accounts.cpp" + "ActivityLog.cpp" + "BugReports.cpp" + "CharInfo.cpp" + "CharXml.cpp" + "CommandLog.cpp" + "Friends.cpp" + "Leaderboard.cpp" + "Mail.cpp" + "MigrationHistory.cpp" + "ObjectIdTracker.cpp" + "PetNames.cpp" + "PlayerCheatDetections.cpp" + "PlayKeys.cpp" + "Property.cpp" + "PropertyContents.cpp" + "Servers.cpp" + "Ugc.cpp" + PARENT_SCOPE +) + diff --git a/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp new file mode 100644 index 00000000..7406e69b --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp @@ -0,0 +1,78 @@ +#include "MySQLDatabase.h" + +std::vector MySQLDatabase::GetApprovedCharacterNames() { + auto result = ExecuteSelect("SELECT name FROM charinfo;"); + + std::vector toReturn; + + while (result->next()) { + toReturn.push_back(result->getString("name").c_str()); + } + + return toReturn; +} + +std::optional CharInfoFromQueryResult(std::unique_ptr stmt) { + if (!stmt->next()) { + return std::nullopt; + } + + ICharInfo::Info toReturn; + + toReturn.id = stmt->getUInt("id"); + toReturn.name = stmt->getString("name").c_str(); + toReturn.pendingName = stmt->getString("pending_name").c_str(); + toReturn.needsRename = stmt->getBoolean("needs_rename"); + toReturn.cloneId = stmt->getUInt64("prop_clone_id"); + toReturn.accountId = stmt->getUInt("account_id"); + toReturn.permissionMap = static_cast(stmt->getUInt("permission_map")); + + return toReturn; +} + +std::optional MySQLDatabase::GetCharacterInfo(const uint32_t charId) { + return CharInfoFromQueryResult( + ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId) + ); +} + +std::optional MySQLDatabase::GetCharacterInfo(const std::string_view name) { + return CharInfoFromQueryResult( + ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name) + ); +} + +std::vector MySQLDatabase::GetAccountCharacterIds(const uint32_t accountId) { + auto result = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId); + + std::vector toReturn; + toReturn.reserve(result->rowsCount()); + while (result->next()) { + toReturn.push_back(result->getUInt("id")); + } + + return toReturn; +} + +void MySQLDatabase::InsertNewCharacter(const ICharInfo::Info info) { + ExecuteInsert( + "INSERT INTO `charinfo`(`id`, `account_id`, `name`, `pending_name`, `needs_rename`, `last_login`) VALUES (?,?,?,?,?,?)", + info.id, + info.accountId, + info.name, + info.pendingName, + false, + static_cast(time(NULL))); +} + +void MySQLDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) { + ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1;", name, static_cast(time(NULL)), characterId); +} + +void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) { + ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1", name, static_cast(time(NULL)), characterId); +} + +void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { + ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast(time(NULL)), characterId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp b/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp new file mode 100644 index 00000000..91a6351e --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp @@ -0,0 +1,19 @@ +#include "MySQLDatabase.h" + +std::string MySQLDatabase::GetCharacterXml(const uint32_t charId) { + auto result = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId); + + if (!result->next()) { + return ""; + } + + return result->getString("xml_data").c_str(); +} + +void MySQLDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) { + ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId); +} + +void MySQLDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) { + ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp b/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp new file mode 100644 index 00000000..c8ae365a --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp @@ -0,0 +1,5 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) { + ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp b/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp new file mode 100644 index 00000000..da9b34a3 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp @@ -0,0 +1,73 @@ +#include "MySQLDatabase.h" + +std::vector MySQLDatabase::GetFriendsList(const uint32_t charId) { + auto friendsList = ExecuteSelect( + R"QUERY( + SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM + ( + SELECT CASE + WHEN player_id = ? THEN friend_id + WHEN friend_id = ? THEN player_id + END AS requested_player, best_friend FROM friends + ) AS fr + JOIN charinfo AS ci ON ci.id = fr.requested_player + WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?; + )QUERY", charId, charId, charId); + + std::vector toReturn; + toReturn.reserve(friendsList->rowsCount()); + + while (friendsList->next()) { + FriendData fd; + fd.friendID = friendsList->getUInt("player"); + fd.isBestFriend = friendsList->getInt("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs + fd.friendName = friendsList->getString("name").c_str(); + + toReturn.push_back(fd); + } + + return toReturn; +} + +std::optional MySQLDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + auto result = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); + + if (!result->next()) { + return std::nullopt; + } + + IFriends::BestFriendStatus toReturn; + toReturn.playerCharacterId = result->getUInt("player_id"); + toReturn.friendCharacterId = result->getUInt("friend_id"); + toReturn.bestFriendStatus = result->getUInt("best_friend"); + + return toReturn; +} + +void MySQLDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) { + ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", + bestFriendStatus, + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); +} + +void MySQLDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + ExecuteInsert("INSERT IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId); +} + +void MySQLDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { + ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", + playerCharacterId, + friendCharacterId, + friendCharacterId, + playerCharacterId + ); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp new file mode 100644 index 00000000..22403abb --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp @@ -0,0 +1,11 @@ +#include "MySQLDatabase.h" + +std::optional MySQLDatabase::GetDonationTotal(const uint32_t activityId) { + auto donation_total = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId); + + if (!donation_total->next()) { + return std::nullopt; + } + + return donation_total->getUInt("donation_total"); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp new file mode 100644 index 00000000..63f5ceca --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp @@ -0,0 +1,83 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) { + ExecuteInsert( + "INSERT INTO `mail` " + "(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)" + " VALUES (?,?,?,?,?,?,?,?,?,?,?,0)", + mail.senderId, + mail.senderUsername, + mail.receiverId, + mail.recipient, + static_cast(time(NULL)), + mail.subject, + mail.body, + mail.itemID, + mail.itemLOT, + 0, + mail.itemCount); +} + +std::vector MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { + auto res = ExecuteSelect( + "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" + " FROM mail WHERE receiver_id=? limit ?;", + characterId, numberOfMail); + + std::vector toReturn; + toReturn.reserve(res->rowsCount()); + + while (res->next()) { + IMail::MailInfo mail; + mail.id = res->getUInt64("id"); + mail.subject = res->getString("subject").c_str(); + mail.body = res->getString("body").c_str(); + mail.senderUsername = res->getString("sender_name").c_str(); + mail.itemID = res->getUInt("attachment_id"); + mail.itemLOT = res->getInt("attachment_lot"); + mail.itemSubkey = res->getInt("attachment_subkey"); + mail.itemCount = res->getInt("attachment_count"); + mail.timeSent = res->getUInt64("time_sent"); + mail.wasRead = res->getBoolean("was_read"); + + toReturn.push_back(std::move(mail)); + } + + return toReturn; +} + +std::optional MySQLDatabase::GetMail(const uint64_t mailId) { + auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); + + if (!res->next()) { + return std::nullopt; + } + + IMail::MailInfo toReturn; + toReturn.itemLOT = res->getInt("attachment_lot"); + toReturn.itemCount = res->getInt("attachment_count"); + + return toReturn; +} + +uint32_t MySQLDatabase::GetUnreadMailCount(const uint32_t characterId) { + auto res = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId); + + if (!res->next()) { + return 0; + } + + return res->getInt("number_unread"); +} + +void MySQLDatabase::MarkMailRead(const uint64_t mailId) { + ExecuteUpdate("UPDATE mail SET was_read=1 WHERE id=? LIMIT 1;", mailId); +} + +void MySQLDatabase::ClaimMailItem(const uint64_t mailId) { + ExecuteUpdate("UPDATE mail SET attachment_lot=0 WHERE id=? LIMIT 1;", mailId); +} + +void MySQLDatabase::DeleteMail(const uint64_t mailId) { + ExecuteDelete("DELETE FROM mail WHERE id=? LIMIT 1;", mailId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/MigrationHistory.cpp b/dDatabase/GameDatabase/MySQL/Tables/MigrationHistory.cpp new file mode 100644 index 00000000..a0afc341 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/MigrationHistory.cpp @@ -0,0 +1,13 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::CreateMigrationHistoryTable() { + ExecuteInsert("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());"); +} + +bool MySQLDatabase::IsMigrationRun(const std::string_view str) { + return ExecuteSelect("SELECT name FROM migration_history WHERE name = ?;", str)->next(); +} + +void MySQLDatabase::InsertMigration(const std::string_view str) { + ExecuteInsert("INSERT INTO migration_history (name) VALUES (?);", str); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp new file mode 100644 index 00000000..f22cd855 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp @@ -0,0 +1,17 @@ +#include "MySQLDatabase.h" + +std::optional MySQLDatabase::GetCurrentPersistentId() { + auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); + if (!result->next()) { + return std::nullopt; + } + return result->getUInt("last_object_id"); +} + +void MySQLDatabase::InsertDefaultPersistentId() { + ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); +} + +void MySQLDatabase::UpdatePersistentId(const uint32_t newId) { + ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PetNames.cpp b/dDatabase/GameDatabase/MySQL/Tables/PetNames.cpp new file mode 100644 index 00000000..dd2a3a6c --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/PetNames.cpp @@ -0,0 +1,26 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) { + ExecuteInsert( + "INSERT INTO `pet_names` (`id`, `pet_name`, `approved`) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE pet_name = ?, approved = ?;", + petId, + info.petName, + info.approvalStatus, + info.petName, + info.approvalStatus); +} + +std::optional MySQLDatabase::GetPetNameInfo(const LWOOBJID& petId) { + auto result = ExecuteSelect("SELECT pet_name, approved FROM pet_names WHERE id = ? LIMIT 1;", petId); + + if (!result->next()) { + return std::nullopt; + } + + IPetNames::Info toReturn; + toReturn.petName = result->getString("pet_name").c_str(); + toReturn.approvalStatus = result->getInt("approved"); + + return toReturn; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PlayKeys.cpp b/dDatabase/GameDatabase/MySQL/Tables/PlayKeys.cpp new file mode 100644 index 00000000..63f2822a --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/PlayKeys.cpp @@ -0,0 +1,11 @@ +#include "MySQLDatabase.h" + +std::optional MySQLDatabase::IsPlaykeyActive(const int32_t playkeyId) { + auto keyCheckRes = ExecuteSelect("SELECT active FROM `play_keys` WHERE id=?", playkeyId); + + if (!keyCheckRes->next()) { + return std::nullopt; + } + + return keyCheckRes->getBoolean("active"); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PlayerCheatDetections.cpp b/dDatabase/GameDatabase/MySQL/Tables/PlayerCheatDetections.cpp new file mode 100644 index 00000000..753630c3 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/PlayerCheatDetections.cpp @@ -0,0 +1,7 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& info) { + ExecuteInsert( + "INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)", + info.userId, info.username, info.extraMessage, info.systemAddress); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp new file mode 100644 index 00000000..5d72a3b5 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -0,0 +1,57 @@ +#include "MySQLDatabase.h" + +std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) { + auto propertyEntry = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved " + "FROM properties WHERE zone_id = ? AND clone_id = ?;", mapId, cloneId); + + if (!propertyEntry->next()) { + return std::nullopt; + } + + IProperty::Info toReturn; + toReturn.id = propertyEntry->getUInt64("id"); + toReturn.ownerId = propertyEntry->getUInt64("owner_id"); + toReturn.cloneId = propertyEntry->getUInt64("clone_id"); + toReturn.name = propertyEntry->getString("name").c_str(); + toReturn.description = propertyEntry->getString("description").c_str(); + toReturn.privacyOption = propertyEntry->getInt("privacy_option"); + toReturn.rejectionReason = propertyEntry->getString("rejection_reason").c_str(); + toReturn.lastUpdatedTime = propertyEntry->getUInt("last_updated"); + toReturn.claimedTime = propertyEntry->getUInt("time_claimed"); + toReturn.reputation = propertyEntry->getUInt("reputation"); + toReturn.modApproved = propertyEntry->getUInt("mod_approved"); + + return toReturn; +} + +void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { + ExecuteUpdate("UPDATE properties SET privacy_option = ?, rejection_reason = ?, mod_approved = ? WHERE id = ? LIMIT 1;", + info.privacyOption, + info.rejectionReason, + info.modApproved, + info.id); +} + +void MySQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) { + ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ? LIMIT 1;", info.name, info.description, info.id); +} + +void MySQLDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) { + ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ? LIMIT 1;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID()); +} + +void MySQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) { + auto insertion = ExecuteInsert( + "INSERT INTO properties" + "(id, owner_id, template_id, clone_id, name, description, zone_id, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, performance_cost)" + "VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '', 0, 0.0)", + info.id, + info.ownerId, + templateId, + zoneId.GetCloneID(), + info.name, + info.description, + zoneId.GetMapID() + ); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp new file mode 100644 index 00000000..dba82d56 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -0,0 +1,54 @@ +#include "MySQLDatabase.h" + +std::vector MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) { + auto result = ExecuteSelect("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;", propertyId); + + std::vector toReturn; + toReturn.reserve(result->rowsCount()); + while (result->next()) { + IPropertyContents::Model model; + model.id = result->getUInt64("id"); + model.lot = static_cast(result->getUInt("lot")); + model.position.x = result->getFloat("x"); + model.position.y = result->getFloat("y"); + model.position.z = result->getFloat("z"); + model.rotation.w = result->getFloat("rw"); + model.rotation.x = result->getFloat("rx"); + model.rotation.y = result->getFloat("ry"); + model.rotation.z = result->getFloat("rz"); + model.ugcId = result->getUInt64("ugc_id"); + toReturn.push_back(std::move(model)); + } + return toReturn; +} + +void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) { + try { + ExecuteInsert( + "INSERT INTO properties_contents" + "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 + model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), + model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, + name, "", // Model description. TODO implement this. + 0, // behavior 1. TODO implement this. + 0, // behavior 2. TODO implement this. + 0, // behavior 3. TODO implement this. + 0, // behavior 4. TODO implement this. + 0 // behavior 5. TODO implement this. + ); + } catch (sql::SQLException& e) { + LOG("Error inserting new property model: %s", e.what()); + } +} + +void MySQLDatabase::UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) { + ExecuteUpdate( + "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;", + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId); +} + +void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { + ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Servers.cpp b/dDatabase/GameDatabase/MySQL/Tables/Servers.cpp new file mode 100644 index 00000000..4411ad21 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Servers.cpp @@ -0,0 +1,23 @@ +#include "MySQLDatabase.h" + +void MySQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) { + // We only want our 1 entry anyways, so we can just delete all and reinsert the one we want + // since it would be two queries anyways. + ExecuteDelete("TRUNCATE TABLE servers;"); + ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port); +} + +std::optional MySQLDatabase::GetMasterInfo() { + auto result = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;"); + + if (!result->next()) { + return std::nullopt; + } + + MasterInfo toReturn; + + toReturn.ip = result->getString("ip").c_str(); + toReturn.port = result->getInt("port"); + + return toReturn; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp new file mode 100644 index 00000000..3b62a51b --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -0,0 +1,71 @@ +#include "MySQLDatabase.h" + +std::vector MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) { + auto result = ExecuteSelect( + "SELECT lxfml, u.id FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", + propertyId); + + std::vector toReturn; + + while (result->next()) { + IUgc::Model model; + + // blob is owned by the query, so we need to do a deep copy :/ + std::unique_ptr blob(result->getBlob("lxfml")); + model.lxfmlData << blob->rdbuf(); + model.id = result->getUInt64("id"); + toReturn.push_back(std::move(model)); + } + + return toReturn; +} + +std::vector MySQLDatabase::GetAllUgcModels() { + auto result = ExecuteSelect("SELECT id, lxfml FROM ugc;"); + + std::vector models; + models.reserve(result->rowsCount()); + while (result->next()) { + IUgc::Model model; + model.id = result->getInt64("id"); + + // blob is owned by the query, so we need to do a deep copy :/ + std::unique_ptr blob(result->getBlob("lxfml")); + model.lxfmlData << blob->rdbuf(); + models.push_back(std::move(model)); + } + + return models; +} + +void MySQLDatabase::RemoveUnreferencedUgcModels() { + ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);"); +} + +void MySQLDatabase::InsertNewUgcModel( + std::istringstream& sd0Data, // cant be const sad + const uint32_t blueprintId, + const uint32_t accountId, + const uint32_t characterId) { + const std::istream stream(sd0Data.rdbuf()); + ExecuteInsert( + "INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)", + blueprintId, + accountId, + characterId, + 0, + &stream, + false, + "weedeater.lxfml" + ); +} + +void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) { + ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId); + ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId); +} + +void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) { + const std::istream stream(lxfml.rdbuf()); + ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); +} diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 7259602f..ee04710a 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -25,104 +25,36 @@ 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;" - ); - - 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; - - 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. - m_ZoneCloneID = 0; - - m_Doc = nullptr; - - //Quickly and dirtly parse the xmlData to get the info we need: - DoQuickXMLDataParse(); - - //Set our objectID: - m_ObjectID = m_ID; - GeneralUtils::SetBit(m_ObjectID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(m_ObjectID, eObjectBits::PERSISTENT); - m_ParentUser = parentUser; m_OurEntity = nullptr; - m_BuildMode = false; + m_Doc = nullptr; } Character::~Character() { - delete m_Doc; + if (m_Doc) delete m_Doc; m_Doc = nullptr; + m_OurEntity = nullptr; + m_ParentUser = nullptr; } -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;" - ); +void Character::UpdateInfoFromDatabase() { + auto charInfo = Database::Get()->GetCharacterInfo(m_ID); - 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)); + if (charInfo) { + m_Name = charInfo->name; + m_UnapprovedName = charInfo->pendingName; + m_NameRejected = charInfo->needsRename; + m_PropertyCloneID = charInfo->cloneId; + m_PermissionMap = charInfo->permissionMap; } - 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; + m_XMLData = Database::Get()->GetCharacterXml(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. m_ZoneCloneID = 0; - delete m_Doc; m_Doc = nullptr; //Quickly and dirtly parse the xmlData to get the info we need: @@ -137,6 +69,11 @@ void Character::UpdateFromDatabase() { m_BuildMode = false; } +void Character::UpdateFromDatabase() { + if (m_Doc) delete m_Doc; + UpdateInfoFromDatabase(); +} + void Character::DoQuickXMLDataParse() { if (m_XMLData.size() == 0) return; @@ -406,17 +343,11 @@ void Character::SetIsNewLogin() { void Character::WriteToDatabase() { //Dump our xml into m_XMLData: - auto* printer = new tinyxml2::XMLPrinter(0, true, 0); - m_Doc->Print(printer); - m_XMLData = printer->CStr(); + tinyxml2::XMLPrinter printer(0, true, 0); + m_Doc->Print(&printer); //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; + Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); } void Character::SetPlayerFlag(const uint32_t flagId, const bool value) { diff --git a/dGame/Character.h b/dGame/Character.h index 79ce0c0c..77a52f44 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -458,6 +458,7 @@ public: void SetBillboardVisible(bool visible); private: + void UpdateInfoFromDatabase(); /** * The ID of this character. First 32 bits of the ObjectID. */ diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index b7083ea8..6d928aaa 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -237,7 +237,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r } baseLookup += " LIMIT 1"; LOG_DEBUG("query is %s", baseLookup.c_str()); - std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseLookup)); + std::unique_ptr baseQuery(Database::Get()->CreatePreppedStmt(baseLookup)); baseQuery->setInt(1, this->gameID); std::unique_ptr baseResult(baseQuery->executeQuery()); @@ -250,7 +250,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r std::unique_ptr lookupBuffer = std::make_unique(STRING_LENGTH); int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); DluAssert(res != -1); - std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); + std::unique_ptr query(Database::Get()->CreatePreppedStmt(lookupBuffer.get())); LOG_DEBUG("Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId); query->setInt(1, this->gameID); if (this->infoType == InfoType::Friends) { @@ -301,7 +301,7 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) { const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId); - std::unique_ptr query(Database::CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;")); + std::unique_ptr query(Database::Get()->CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;")); query->setInt(1, playerID); query->setInt(2, activityId); std::unique_ptr myScoreResult(query->executeQuery()); @@ -378,14 +378,14 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi saveQuery = FormatInsert(leaderboardType, newScore, false); } LOG("save query %s %i %i", saveQuery.c_str(), playerID, activityId); - std::unique_ptr saveStatement(Database::CreatePreppedStmt(saveQuery)); + std::unique_ptr saveStatement(Database::Get()->CreatePreppedStmt(saveQuery)); saveStatement->setInt(1, playerID); saveStatement->setInt(2, activityId); saveStatement->execute(); // track wins separately if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) { - std::unique_ptr winUpdate(Database::CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); + std::unique_ptr winUpdate(Database::Get()->CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); winUpdate->setInt(1, playerID); winUpdate->setInt(2, activityId); winUpdate->execute(); diff --git a/dGame/User.cpp b/dGame/User.cpp index afde75f6..ce440bd7 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -23,41 +23,23 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: m_IsBestFriendMap = std::unordered_map(); - //HACK HACK HACK - //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()); - - sql::ResultSet* res = stmt->executeQuery(); - while (res->next()) { - m_AccountID = res->getUInt(1); - m_MaxGMLevel = static_cast(res->getInt(2)); + auto userInfo = Database::Get()->GetAccountInfo(username); + if (userInfo) { + m_AccountID = userInfo->id; + m_MaxGMLevel = userInfo->maxGmLevel; m_MuteExpire = 0; //res->getUInt64(3); } - delete res; - delete stmt; - //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); - - 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); - LOG("Loaded %llu as it is the last used char", objID); - } + auto characterList = Database::Get()->GetAccountCharacterIds(m_AccountID); + if (!characterList.empty()) { + const uint32_t lastUsedCharacterId = characterList.front(); + Character* character = new Character(lastUsedCharacterId, this); + character->UpdateFromDatabase(); + m_Characters.push_back(character); + LOG("Loaded %i as it is the last used char", lastUsedCharacterId); } - - delete res; - delete stmt; } } @@ -92,10 +74,7 @@ User& User::operator= (const User& other) { } bool User::operator== (const User& other) const { - if (m_Username == other.m_Username || m_SessionKey == other.m_SessionKey || m_SystemAddress == other.m_SystemAddress) - return true; - - return false; + return m_Username == other.m_Username || m_SessionKey == other.m_SessionKey || m_SystemAddress == other.m_SystemAddress; } Character* User::GetLastUsedChar() { diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 0b26822d..9820636a 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -158,20 +158,6 @@ void UserManager::DeletePendingRemovals() { m_UsersToDelete.clear(); } -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; -} - std::string UserManager::GetPredefinedName(uint32_t firstNameIndex, uint32_t middleNameIndex, uint32_t lastNameIndex) { if (firstNameIndex > m_FirstNames.size() || middleNameIndex > m_MiddleNames.size() || lastNameIndex > m_LastNames.size()) return std::string("INVALID"); return std::string(m_FirstNames[firstNameIndex] + m_MiddleNames[middleNameIndex] + m_LastNames[lastNameIndex]); @@ -200,11 +186,6 @@ bool UserManager::IsNamePreapproved(const std::string& requestedName) { 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()); - - sql::ResultSet* res = stmt->executeQuery(); std::vector& chars = u->GetCharacters(); for (size_t i = 0; i < chars.size(); ++i) { @@ -232,16 +213,13 @@ 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& characterId : Database::Get()->GetAccountCharacterIds(u->GetAccountID())) { + Character* character = new Character(characterId, u); + character->UpdateFromDatabase(); character->SetIsNewLogin(); chars.push_back(character); } - delete res; - delete stmt; - WorldPackets::SendCharacterList(sysAddr, u); } @@ -270,19 +248,19 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle); LOT pantsLOT = FindCharPantsID(pantsColor); - if (name != "" && !UserManager::IsNameAvailable(name)) { + if (!name.empty() && Database::Get()->GetCharacterInfo(name)) { LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str()); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE); return; } - if (!IsNameAvailable(predefinedName)) { + if (Database::Get()->GetCharacterInfo(predefinedName)) { LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str()); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE); return; } - if (name == "") { + if (name.empty()) { LOG("AccountID: %i is creating a character with predefined name: %s", u->GetAccountID(), predefinedName.c_str()); } else { LOG("AccountID: %i is creating a character with name: %s (temporary: %s)", u->GetAccountID(), name.c_str(), predefinedName.c_str()); @@ -290,13 +268,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()) { - LOG("Character object id unavailable, check objectidtracker!"); + if (Database::Get()->GetCharacterInfo(objectID)) { + LOG("Character object id unavailable, check object_id_tracker!"); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); return; } @@ -337,41 +310,19 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) bool nameOk = IsNamePreapproved(name); if (!nameOk && 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)); + std::string_view nameToAssign = !name.empty() && nameOk ? name : predefinedName; + std::string pendingName = !name.empty() && !nameOk ? name : ""; - if (nameOk) { - stmt->setString(3, name.c_str()); - stmt->setString(4, ""); - } + ICharInfo::Info info; + info.name = nameToAssign; + info.pendingName = pendingName; + info.id = objectID; + info.accountId = u->GetAccountID(); - stmt->execute(); - delete stmt; - } 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::Get()->InsertNewCharacter(info); //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; + Database::Get()->InsertCharacterXml(objectID, xml3.str()); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); @@ -403,73 +354,12 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) WorldPackets::SendCharacterDeleteResponse(sysAddr, false); } else { LOG("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::Get()->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); } @@ -517,26 +407,14 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) return; } - if (IsNameAvailable(newName)) { + if (Database::Get()->GetCharacterInfo(newName)) { if (IsNamePreapproved(newName)) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("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, character->GetID()); - stmt->execute(); - delete stmt; - + Database::Get()->SetCharacterName(charID, newName); LOG("Character %s now known as %s", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); } else { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE charinfo SET pending_name=?, needs_rename=0, last_login=? WHERE id=? LIMIT 1"); - stmt->setString(1, newName); - stmt->setUInt64(2, time(NULL)); - stmt->setUInt(3, character->GetID()); - stmt->execute(); - delete stmt; - + Database::Get()->SetPendingCharacterName(charID, newName); LOG("Character %s has been renamed to %s and is pending approval by a moderator.", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); @@ -566,11 +444,7 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID } if (hasCharacter && character) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE charinfo SET last_login=? WHERE id=? LIMIT 1"); - stmt->setUInt64(1, time(NULL)); - stmt->setUInt(2, playerID); - stmt->execute(); - delete stmt; + Database::Get()->UpdateLastLoggedInCharacter(playerID); uint32_t zoneID = character->GetZoneID(); if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE diff --git a/dGame/UserManager.h b/dGame/UserManager.h index bf9985a1..94bf5e95 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -28,7 +28,6 @@ public: bool DeleteUser(const SystemAddress& sysAddr); //Returns true on succesful deletion void DeletePendingRemovals(); - bool IsNameAvailable(const std::string& requestedName); std::string GetPredefinedName(uint32_t firstNameIndex, uint32_t middleNameIndex, uint32_t lastNameIndex); bool IsNamePreapproved(const std::string& requestedName); diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 7a30d821..8c59b28e 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -700,7 +700,7 @@ std::string CharacterComponent::StatisticsToString() const { } uint64_t CharacterComponent::GetStatisticFromSplit(std::vector split, uint32_t index) { - return split.size() > index ? std::stoul(split.at(index)) : 0; + return split.size() > index ? std::stoull(split.at(index)) : 0; } ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) { diff --git a/dGame/dComponents/DonationVendorComponent.cpp b/dGame/dComponents/DonationVendorComponent.cpp index 7f85ea97..6abc959a 100644 --- a/dGame/dComponents/DonationVendorComponent.cpp +++ b/dGame/dComponents/DonationVendorComponent.cpp @@ -22,10 +22,8 @@ DonationVendorComponent::DonationVendorComponent(Entity* parent) : VendorCompone return; } - std::unique_ptr query(Database::CreatePreppedStmt("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;")); - query->setInt(1, m_ActivityId); - std::unique_ptr donation_total(query->executeQuery()); - if (donation_total->next()) m_TotalDonated = donation_total->getInt("donation_total"); + auto donationTotal = Database::Get()->GetDonationTotal(m_ActivityId); + if (donationTotal) m_TotalDonated = donationTotal.value(); m_TotalRemaining = m_Goal - m_TotalDonated; m_PercentComplete = m_TotalDonated/static_cast(m_Goal); } diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index df473055..1780437e 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -1097,37 +1097,18 @@ void PetComponent::SetPetNameForModeration(const std::string& petName) { approved = 2; //approved } - auto deleteStmt = Database::CreatePreppedStmt("DELETE FROM pet_names WHERE id = ? LIMIT 1;"); - deleteStmt->setUInt64(1, 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; + Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{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); - + auto petNameInfo = Database::Get()->GetPetNameInfo(m_DatabaseId); + if (petNameInfo) { + m_ModerationStatus = petNameInfo->approvalStatus; if (m_ModerationStatus == 2) { - m_Name = res->getString(1); + m_Name = petNameInfo->petName; } } - - delete res; - delete stmt; } void PetComponent::SetPreconditions(std::string& preconditions) { diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index d8b3eb95..4d7ea16d 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -103,7 +103,7 @@ std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMe if (sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS) { std::string friendsList = " AND p.owner_id IN ("; - auto friendsListQuery = Database::CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;"); + auto friendsListQuery = Database::Get()->CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;"); friendsListQuery->setUInt(1, character->GetID()); friendsListQuery->setUInt(2, character->GetID()); @@ -147,7 +147,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl if (!character) return; // Player property goes in index 1 of the vector. This is how the client expects it. - auto playerPropertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE owner_id = ? AND zone_id = ?"); + auto playerPropertyLookup = Database::Get()->CreatePreppedStmt("SELECT * FROM properties WHERE owner_id = ? AND zone_id = ?"); playerPropertyLookup->setInt(1, character->GetID()); playerPropertyLookup->setInt(2, this->m_MapID); @@ -180,7 +180,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl const auto query = BuildQuery(entity, sortMethod, character); - auto propertyLookup = Database::CreatePreppedStmt(query); + auto propertyLookup = Database::Get()->CreatePreppedStmt(query); const auto searchString = "%" + filterText + "%"; propertyLookup->setUInt(1, this->m_MapID); @@ -209,7 +209,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl std::string ownerName = ""; bool isOwned = true; - auto nameLookup = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); + auto nameLookup = Database::Get()->CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); nameLookup->setUInt64(1, cloneId); @@ -245,7 +245,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl GeneralUtils::SetBit(ownerObjId, eObjectBits::PERSISTENT); // Query to get friend and best friend fields - auto friendCheck = Database::CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)"); + auto friendCheck = Database::Get()->CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)"); friendCheck->setUInt(1, character->GetID()); friendCheck->setUInt(2, ownerObjId); @@ -278,7 +278,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl bool isAlt = false; // Query to determine whether this property is an alt character of the entity. - auto isAltQuery = Database::CreatePreppedStmt("SELECT id FROM charinfo where account_id in (SELECT account_id from charinfo WHERE id = ?) AND id = ?;"); + auto isAltQuery = Database::Get()->CreatePreppedStmt("SELECT id FROM charinfo where account_id in (SELECT account_id from charinfo WHERE id = ?) AND id = ?;"); isAltQuery->setInt(1, character->GetID()); isAltQuery->setInt(2, owner); @@ -312,7 +312,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl int32_t numberOfProperties = 0; auto buttonQuery = BuildQuery(entity, sortMethod, character, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false); - auto propertiesLeft = Database::CreatePreppedStmt(buttonQuery); + auto propertiesLeft = Database::Get()->CreatePreppedStmt(buttonQuery); propertiesLeft->setUInt(1, this->m_MapID); propertiesLeft->setString(2, searchString.c_str()); diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 1fc38119..7877b2c9 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -39,13 +39,12 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo instance = this; const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); - const auto zoneId = worldId.GetMapID(); const auto cloneId = worldId.GetCloneID(); - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT id FROM PropertyTemplate WHERE mapID = ?;"); - query.bind(1, (int)zoneId); + auto query = CDClientDatabase::CreatePreppedStmt("SELECT id FROM PropertyTemplate WHERE mapID = ?;"); + + query.bind(1, static_cast(zoneId)); auto result = query.execQuery(); @@ -55,34 +54,25 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo templateId = result.getIntField(0); - result.finalize(); + auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); - auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;"); - - propertyLookup->setInt(1, templateId); - propertyLookup->setInt64(2, cloneId); - - auto* propertyEntry = propertyLookup->executeQuery(); - - if (propertyEntry->next()) { - this->propertyId = propertyEntry->getUInt64(1); - this->owner = propertyEntry->getUInt64(2); + if (propertyInfo) { + this->propertyId = propertyInfo->id; + this->owner = propertyInfo->ownerId; GeneralUtils::SetBit(this->owner, eObjectBits::CHARACTER); GeneralUtils::SetBit(this->owner, eObjectBits::PERSISTENT); - this->clone_Id = propertyEntry->getInt(2); - this->propertyName = propertyEntry->getString(5).c_str(); - this->propertyDescription = propertyEntry->getString(6).c_str(); - this->privacyOption = static_cast(propertyEntry->getUInt(9)); - this->moderatorRequested = propertyEntry->getInt(10) == 0 && rejectionReason == "" && privacyOption == PropertyPrivacyOption::Public; - this->LastUpdatedTime = propertyEntry->getUInt64(11); - this->claimedTime = propertyEntry->getUInt64(12); - this->rejectionReason = std::string(propertyEntry->getString(13).c_str()); - this->reputation = propertyEntry->getUInt(14); + this->clone_Id = propertyInfo->cloneId; + this->propertyName = propertyInfo->name; + this->propertyDescription = propertyInfo->description; + this->privacyOption = static_cast(propertyInfo->privacyOption); + this->rejectionReason = propertyInfo->rejectionReason; + this->moderatorRequested = propertyInfo->modApproved == 0 && rejectionReason == "" && privacyOption == PropertyPrivacyOption::Public; + this->LastUpdatedTime = propertyInfo->lastUpdatedTime; + this->claimedTime = propertyInfo->claimedTime; + this->reputation = propertyInfo->reputation; Load(); } - - delete propertyLookup; } LWOOBJID PropertyManagementComponent::GetOwnerId() const { @@ -152,14 +142,13 @@ void PropertyManagementComponent::SetPrivacyOption(PropertyPrivacyOption value) } privacyOption = value; - auto* propertyUpdate = Database::CreatePreppedStmt("UPDATE properties SET privacy_option = ?, rejection_reason = ?, mod_approved = ? WHERE id = ?;"); + IProperty::Info info; + info.id = propertyId; + info.privacyOption = static_cast(privacyOption); + info.rejectionReason = rejectionReason; + info.modApproved = 0; - propertyUpdate->setInt(1, static_cast(value)); - propertyUpdate->setString(2, ""); - propertyUpdate->setInt(3, 0); - propertyUpdate->setInt64(4, propertyId); - - propertyUpdate->executeUpdate(); + Database::Get()->UpdatePropertyModerationInfo(info); } void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::string description) { @@ -169,13 +158,12 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s propertyDescription = description; - auto* propertyUpdate = Database::CreatePreppedStmt("UPDATE properties SET name = ?, description = ? WHERE id = ?;"); + IProperty::Info info; + info.id = propertyId; + info.name = propertyName; + info.description = propertyDescription; - propertyUpdate->setString(1, name.c_str()); - propertyUpdate->setString(2, description.c_str()); - propertyUpdate->setInt64(3, propertyId); - - propertyUpdate->executeUpdate(); + Database::Get()->UpdatePropertyDetails(info); OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS); } @@ -217,28 +205,14 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) { propertyId = ObjectIDManager::GenerateRandomObjectID(); - auto* insertion = Database::CreatePreppedStmt( - "INSERT INTO properties" - "(id, owner_id, template_id, clone_id, name, description, rent_amount, rent_due, privacy_option, last_updated, time_claimed, rejection_reason, reputation, zone_id, performance_cost)" - "VALUES (?, ?, ?, ?, ?, ?, 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '', 0, ?, 0.0)" - ); - insertion->setUInt64(1, propertyId); - insertion->setUInt64(2, (uint32_t)playerId); - insertion->setUInt(3, templateId); - insertion->setUInt64(4, playerCloneId); - insertion->setString(5, name.c_str()); - insertion->setString(6, description.c_str()); - insertion->setInt(7, propertyZoneId); + IProperty::Info info; + info.id = propertyId; + info.ownerId = character->GetID(); + info.cloneId = playerCloneId; + info.name = name; + info.description = description; - // Try and execute the query, print an error if it fails. - try { - insertion->execute(); - } catch (sql::SQLException& exception) { - LOG("Failed to execute query: (%s)!", exception.what()); - - throw exception; - return false; - } + Database::Get()->InsertNewProperty(info, templateId, worldId); auto* zoneControlObject = Game::zoneManager->GetZoneControlObject(); for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControlObject)) { @@ -545,7 +519,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet { item->SetCount(item->GetCount() - 1); - LOG("YES IT GOES HERE"); + LOG("BODGE TIME, YES IT GOES HERE"); break; } @@ -569,14 +543,13 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet void PropertyManagementComponent::UpdateApprovedStatus(const bool value) { if (owner == LWOOBJID_EMPTY) return; - auto* update = Database::CreatePreppedStmt("UPDATE properties SET mod_approved = ? WHERE id = ?;"); + IProperty::Info info; + info.id = propertyId; + info.modApproved = value; + info.privacyOption = static_cast(privacyOption); + info.rejectionReason = ""; - update->setBoolean(1, value); - update->setInt64(2, propertyId); - - update->executeUpdate(); - - delete update; + Database::Get()->UpdatePropertyModerationInfo(info); } void PropertyManagementComponent::Load() { @@ -584,39 +557,17 @@ void PropertyManagementComponent::Load() { return; } - auto* lookup = Database::CreatePreppedStmt("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;"); - - lookup->setUInt64(1, propertyId); - - auto* lookupResult = lookup->executeQuery(); - - while (lookupResult->next()) { - const LWOOBJID id = lookupResult->getUInt64(1); - const LOT lot = lookupResult->getInt(2); - - const NiPoint3 position = - { - static_cast(lookupResult->getDouble(3)), - static_cast(lookupResult->getDouble(4)), - static_cast(lookupResult->getDouble(5)) - }; - - const NiQuaternion rotation = - { - static_cast(lookupResult->getDouble(9)), - static_cast(lookupResult->getDouble(6)), - static_cast(lookupResult->getDouble(7)), - static_cast(lookupResult->getDouble(8)) - }; + auto propertyModels = Database::Get()->GetPropertyModels(propertyId); + for (const auto& databaseModel : propertyModels) { auto* node = new SpawnerNode(); - node->position = position; - node->rotation = rotation; + node->position = databaseModel.position; + node->rotation = databaseModel.rotation; SpawnerInfo info{}; - info.templateID = lot; + info.templateID = databaseModel.lot; info.nodes = { node }; info.templateScale = 1.0f; info.activeOnLoad = true; @@ -626,13 +577,13 @@ void PropertyManagementComponent::Load() { //info.emulated = true; //info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - info.spawnerID = id; + info.spawnerID = databaseModel.id; std::vector settings; //BBB property models need to have extra stuff set for them: - if (lot == 14) { - LWOOBJID blueprintID = lookupResult->getUInt(10); + if (databaseModel.lot == 14) { + LWOOBJID blueprintID = databaseModel.ugcId; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); @@ -640,7 +591,7 @@ void PropertyManagementComponent::Load() { LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); LDFBaseData* modelType = new LDFData(u"modelType", 2); LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(u"userModelID", id); + LDFBaseData* userModelID = new LDFData(u"userModelID", databaseModel.id); settings.push_back(ldfBlueprintID); settings.push_back(componentWhitelist); @@ -649,7 +600,7 @@ void PropertyManagementComponent::Load() { settings.push_back(userModelID); } else { auto modelType = new LDFData(u"modelType", 2); - auto userModelID = new LDFData(u"userModelID", id); + auto userModelID = new LDFData(u"userModelID", databaseModel.id); auto ldfModelBehavior = new LDFData(u"modelBehaviors", 0); auto propertyObjectID = new LDFData(u"propertyObjectID", true); auto componentWhitelist = new LDFData(u"componentWhitelist", 1); @@ -671,8 +622,6 @@ void PropertyManagementComponent::Load() { models.insert_or_assign(model->GetObjectID(), spawnerId); } - - delete lookup; } void PropertyManagementComponent::Save() { @@ -680,27 +629,7 @@ void PropertyManagementComponent::Save() { return; } - auto* insertion = Database::CreatePreppedStmt("INSERT INTO properties_contents VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"); - auto* update = Database::CreatePreppedStmt("UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;"); - auto* lookup = Database::CreatePreppedStmt("SELECT id FROM properties_contents WHERE property_id = ?;"); - auto* remove = Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE id = ?;"); - - lookup->setUInt64(1, propertyId); - sql::ResultSet* lookupResult = nullptr; - try { - lookupResult = lookup->executeQuery(); - } catch (sql::SQLException& ex) { - LOG("lookup error %s", ex.what()); - } - std::vector present; - - while (lookupResult->next()) { - const auto dbId = lookupResult->getUInt64(1); - - present.push_back(dbId); - } - - delete lookupResult; + auto present = Database::Get()->GetPropertyModels(propertyId); std::vector modelIds; @@ -719,69 +648,26 @@ void PropertyManagementComponent::Save() { const auto rotation = entity->GetRotation(); if (std::find(present.begin(), present.end(), id) == present.end()) { - insertion->setInt64(1, id); - insertion->setUInt64(2, propertyId); - insertion->setNull(3, 0); - insertion->setInt(4, entity->GetLOT()); - insertion->setDouble(5, position.x); - insertion->setDouble(6, position.y); - insertion->setDouble(7, position.z); - insertion->setDouble(8, rotation.x); - insertion->setDouble(9, rotation.y); - insertion->setDouble(10, rotation.z); - insertion->setDouble(11, rotation.w); - insertion->setString(12, ("Objects_" + std::to_string(entity->GetLOT()) + "_name").c_str()); // Model name. TODO make this customizable - insertion->setString(13, ""); // Model description. TODO implement this. - insertion->setDouble(14, 0); // behavior 1. TODO implement this. - insertion->setDouble(15, 0); // behavior 2. TODO implement this. - insertion->setDouble(16, 0); // behavior 3. TODO implement this. - insertion->setDouble(17, 0); // behavior 4. TODO implement this. - insertion->setDouble(18, 0); // behavior 5. TODO implement this. - try { - insertion->execute(); - } catch (sql::SQLException& ex) { - LOG("Error inserting into properties_contents. Error %s", ex.what()); - } - } else { - update->setDouble(1, position.x); - update->setDouble(2, position.y); - update->setDouble(3, position.z); - update->setDouble(4, rotation.x); - update->setDouble(5, rotation.y); - update->setDouble(6, rotation.z); - update->setDouble(7, rotation.w); + IPropertyContents::Model model; + model.id = id; + model.lot = entity->GetLOT(); + model.position = position; + model.rotation = rotation; + model.ugcId = 0; - update->setInt64(8, id); - try { - update->executeUpdate(); - } catch (sql::SQLException& ex) { - LOG("Error updating properties_contents. Error: %s", ex.what()); - } + Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_" + std::to_string(model.lot) + "_name"); + } else { + Database::Get()->UpdateModelPositionRotation(id, position, rotation); } } - for (auto id : present) { - if (std::find(modelIds.begin(), modelIds.end(), id) != modelIds.end()) { + for (auto model : present) { + if (std::find(modelIds.begin(), modelIds.end(), model.id) != modelIds.end()) { continue; } - remove->setInt64(1, id); - try { - remove->execute(); - } catch (sql::SQLException& ex) { - LOG("Error removing from properties_contents. Error %s", ex.what()); - } + Database::Get()->RemoveModel(model.id); } - - auto* removeUGC = Database::CreatePreppedStmt("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents);"); - - removeUGC->execute(); - - delete removeUGC; - delete insertion; - delete update; - delete lookup; - delete remove; } void PropertyManagementComponent::AddModel(LWOOBJID modelId, LWOOBJID spawnerId) { @@ -799,6 +685,7 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); const auto zoneId = worldId.GetMapID(); + const auto cloneId = worldId.GetCloneID(); LOG("Getting property info for %d", zoneId); GameMessages::PropertyDataMessage message = GameMessages::PropertyDataMessage(zoneId); @@ -806,45 +693,25 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const const auto isClaimed = GetOwnerId() != LWOOBJID_EMPTY; LWOOBJID ownerId = GetOwnerId(); - std::string ownerName = ""; + std::string ownerName; + auto charInfo = Database::Get()->GetCharacterInfo(ownerId); + if (charInfo) ownerName = charInfo->name; std::string name = ""; std::string description = ""; uint64_t claimed = 0; char privacy = 0; if (isClaimed) { - const auto cloneId = worldId.GetCloneID(); - - auto* nameLookup = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); - nameLookup->setUInt64(1, cloneId); - - auto* nameResult = nameLookup->executeQuery(); - if (nameResult->next()) { - ownerName = nameResult->getString(1).c_str(); - } - - delete nameResult; - delete nameLookup; - name = propertyName; description = propertyDescription; claimed = claimedTime; privacy = static_cast(this->privacyOption); if (moderatorRequested) { - auto checkStatus = Database::CreatePreppedStmt("SELECT rejection_reason, mod_approved FROM properties WHERE id = ?;"); - - checkStatus->setInt64(1, propertyId); - - auto result = checkStatus->executeQuery(); - - result->next(); - - const auto reason = std::string(result->getString(1).c_str()); - const auto modApproved = result->getInt(2); - if (reason != "") { + auto moderationInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); + if (moderationInfo->rejectionReason != "") { moderatorRequested = false; - rejectionReason = reason; - } else if (reason == "" && modApproved == 1) { + rejectionReason = moderationInfo->rejectionReason; + } else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) { moderatorRequested = false; rejectionReason = ""; } else { diff --git a/dGame/dGameMessages/GameMessageHandler.h b/dGame/dGameMessages/GameMessageHandler.h index 821d80be..61378f80 100644 --- a/dGame/dGameMessages/GameMessageHandler.h +++ b/dGame/dGameMessages/GameMessageHandler.h @@ -18,7 +18,7 @@ #include "Game.h" #include "Logger.h" #include "GameMessages.h" -#include "../dDatabase/CDClientDatabase.h" +#include "CDClientDatabase.h" enum class eGameMessageType : uint16_t; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 82a7ee77..7fb9c259 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2577,70 +2577,23 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent const auto zoneId = worldId.GetMapID(); const auto cloneId = worldId.GetCloneID(); - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT id FROM PropertyTemplate WHERE mapID = ?;"); - query.bind(1, (int)zoneId); - - auto result = query.execQuery(); - - if (result.eof() || result.fieldIsNull(0)) return; - - int templateId = result.getIntField(0); - - auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;"); - - propertyLookup->setInt(1, templateId); - propertyLookup->setInt64(2, cloneId); - - auto* propertyEntry = propertyLookup->executeQuery(); - uint64_t propertyId = 0; - - if (propertyEntry->next()) { - propertyId = propertyEntry->getUInt64(1); - } - - delete propertyEntry; - delete propertyLookup; + auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); + LWOOBJID propertyId = LWOOBJID_EMPTY; + if (propertyInfo) propertyId = propertyInfo->id; //Insert into ugc: - auto ugcs = Database::CreatePreppedStmt("INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)"); - ugcs->setUInt(1, blueprintIDSmall); - ugcs->setInt(2, entity->GetParentUser()->GetAccountID()); - ugcs->setInt(3, entity->GetCharacter()->GetID()); - ugcs->setInt(4, 0); - - //whacky stream biz - std::string s(sd0Data.get(), sd0Size); - std::istringstream iss(s); - - ugcs->setBlob(5, &iss); - ugcs->setBoolean(6, false); - ugcs->setString(7, "weedeater.lxfml"); - ugcs->execute(); - delete ugcs; + std::string str(sd0Data.get(), sd0Size); + std::istringstream sd0DataStream(str); + Database::Get()->InsertNewUgcModel(sd0DataStream, blueprintIDSmall, entity->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); //Insert into the db as a BBB model: - auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - stmt->setUInt64(1, newIDL); - stmt->setUInt64(2, propertyId); - stmt->setUInt(3, blueprintIDSmall); - stmt->setUInt(4, 14); // 14 is the lot the BBB models use - stmt->setDouble(5, 0.0f); // x - stmt->setDouble(6, 0.0f); // y - stmt->setDouble(7, 0.0f); // z - stmt->setDouble(8, 0.0f); // rx - stmt->setDouble(9, 0.0f); // ry - stmt->setDouble(10, 0.0f); // rz - stmt->setDouble(11, 0.0f); // rw - stmt->setString(12, "Objects_14_name"); // Model name. TODO make this customizable - stmt->setString(13, ""); // Model description. TODO implement this. - stmt->setDouble(14, 0); // behavior 1. TODO implement this. - stmt->setDouble(15, 0); // behavior 2. TODO implement this. - stmt->setDouble(16, 0); // behavior 3. TODO implement this. - stmt->setDouble(17, 0); // behavior 4. TODO implement this. - stmt->setDouble(18, 0); // behavior 5. TODO implement this. - stmt->execute(); - delete stmt; + IPropertyContents::Model model; + model.id = newIDL; + model.ugcId = blueprintIDSmall; + model.position = NiPoint3::ZERO; + model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + model.lot = 14; + Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name"); /* Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. @@ -4170,6 +4123,13 @@ void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, racingControlComponent->OnRequestDie(entity); } + else { + auto* destroyableComponent = entity->GetComponent(); + + if (!destroyableComponent) return; + + destroyableComponent->Smash(killerID, killType, deathType); + } } @@ -4225,20 +4185,12 @@ void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream* inStre if (performanceCost == 0.0f) return; auto zone = Game::zoneManager->GetZone(); - const auto& worldId = zone->GetZoneID(); - const auto cloneId = worldId.GetCloneID(); - const auto zoneId = worldId.GetMapID(); + if (!zone) { + LOG("If you see this message, something is very wrong."); + return; + } - auto updatePerformanceCostQuery = Database::CreatePreppedStmt("UPDATE properties SET performance_cost = ? WHERE clone_id = ? AND zone_id = ?"); - - updatePerformanceCostQuery->setDouble(1, performanceCost); - updatePerformanceCostQuery->setInt(2, cloneId); - updatePerformanceCostQuery->setInt(3, zoneId); - - updatePerformanceCostQuery->executeUpdate(); - - delete updatePerformanceCostQuery; - updatePerformanceCostQuery = nullptr; + Database::Get()->UpdatePerformanceCost(zone->GetZoneID(), performanceCost); } void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -5119,7 +5071,7 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream* inStream, E item->Disassemble(TEMP_MODELS); - std::unique_ptr stmt(Database::CreatePreppedStmt("DELETE FROM ugc_modular_build where ugc_id = ?")); + std::unique_ptr stmt(Database::Get()->CreatePreppedStmt("DELETE FROM ugc_modular_build where ugc_id = ?")); stmt->setUInt64(1, item->GetSubKey()); stmt->execute(); @@ -5622,7 +5574,7 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity* inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); } - std::unique_ptr stmt(Database::CreatePreppedStmt("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)")); + std::unique_ptr stmt(Database::Get()->CreatePreppedStmt("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)")); stmt->setUInt64(1, newIdBig); stmt->setString(2, GeneralUtils::UTF16ToWTF8(modules)); auto* pCharacter = character->GetCharacter(); @@ -5979,31 +5931,27 @@ void GameMessages::SendGetHotPropertyData(RakNet::BitStream* inStream, Entity* e void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) { //Definitely not stolen from autogenerated code, no sir: - std::u16string body; - std::string clientVersion; - std::string nOtherPlayerID; - std::string selection; - uint32_t messageLength; - int32_t reporterID = 0; + IBugReports::Info reportInfo; //Reading: + uint32_t messageLength; inStream->Read(messageLength); for (uint32_t i = 0; i < (messageLength); ++i) { uint16_t character; inStream->Read(character); - body.push_back(character); + reportInfo.body.push_back(static_cast(character)); } auto character = entity->GetCharacter(); - if (character) reporterID = character->GetID(); + if (character) reportInfo.characterId = character->GetID(); uint32_t clientVersionLength; inStream->Read(clientVersionLength); for (unsigned int k = 0; k < clientVersionLength; k++) { unsigned char character; inStream->Read(character); - clientVersion.push_back(character); + reportInfo.clientVersion.push_back(character); } uint32_t nOtherPlayerIDLength; @@ -6011,32 +5959,18 @@ void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) for (unsigned int k = 0; k < nOtherPlayerIDLength; k++) { unsigned char character; inStream->Read(character); - nOtherPlayerID.push_back(character); + reportInfo.otherPlayer.push_back(character); } - // Convert other player id from LWOOBJID to the database id. - uint32_t otherPlayer = LWOOBJID_EMPTY; - if (nOtherPlayerID != "") otherPlayer = std::atoi(nOtherPlayerID.c_str()); uint32_t selectionLength; inStream->Read(selectionLength); for (unsigned int k = 0; k < selectionLength; k++) { unsigned char character; inStream->Read(character); - selection.push_back(character); + reportInfo.selection.push_back(character); } - try { - sql::PreparedStatement* insertBug = Database::CreatePreppedStmt("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)"); - insertBug->setString(1, GeneralUtils::UTF16ToWTF8(body)); - insertBug->setString(2, clientVersion); - insertBug->setString(3, std::to_string(otherPlayer)); - insertBug->setString(4, selection); - insertBug->setInt(5, reporterID); - insertBug->execute(); - delete insertBug; - } catch (sql::SQLException& e) { - LOG("Couldn't save bug report! (%s)", e.what()); - } + Database::Get()->InsertNewBugReport(reportInfo); } void diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index ce841710..a43cdaeb 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -9,6 +9,7 @@ #include "User.h" #include "UserManager.h" #include "dConfig.h" +#include Entity* GetPossessedEntity(const LWOOBJID& objId) { auto* entity = Game::entityManager->GetEntity(objId); @@ -26,20 +27,22 @@ void ReportCheat(User* user, const SystemAddress& sysAddr, const char* messageIf if (!user) { LOG("WARNING: User is null, using defaults."); } - std::unique_ptr stmt(Database::CreatePreppedStmt( - "INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)") - ); - user ? stmt->setInt(1, user->GetAccountID()) : stmt->setNull(1, sql::DataType::INTEGER); - stmt->setString(2, user ? user->GetUsername().c_str() : "User is null."); + + IPlayerCheatDetections::Info info; + if (user) info.userId = user->GetAccountID(); + info.username = user ? user->GetUsername().c_str() : "User is null."; + + // user string here because ToString is static and may change. + info.systemAddress = sysAddr.ToString(); constexpr int32_t bufSize = 4096; - char buffer[bufSize]; - vsnprintf(buffer, bufSize, messageIfNotSender, args); + char extraMsg[bufSize]; + vsnprintf(extraMsg, bufSize, messageIfNotSender, args); + info.extraMessage = extraMsg; - stmt->setString(3, buffer); - stmt->setString(4, Game::config->GetValue("log_ip_addresses_for_anti_cheat") == "1" ? sysAddr.ToString() : "IP logging disabled."); - stmt->execute(); - LOG("Anti-cheat message: %s", buffer); + Database::Get()->InsertCheatDetection(info); + + LOG("Anti-cheat message: %s", extraMsg); } void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sysAddr, const CheckType checkType, const char* messageIfNotSender, va_list args) { diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 961dc5f6..2677c9cc 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -76,22 +76,19 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient, const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) { - auto* ins = Database::CreatePreppedStmt("INSERT INTO `mail`(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`) VALUES (?,?,?,?,?,?,?,?,?,?,?,0)"); + IMail::MailInfo mailInsert; + mailInsert.senderUsername = senderName; + mailInsert.recipient = recipientName; + mailInsert.subject = subject; + mailInsert.body = body; + mailInsert.senderId = sender; + mailInsert.receiverId = recipient; + mailInsert.itemCount = attachmentCount; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = attachment; + mailInsert.itemSubkey = LWOOBJID_EMPTY; - ins->setUInt(1, sender); - ins->setString(2, senderName.c_str()); - ins->setUInt(3, recipient); - ins->setString(4, recipientName.c_str()); - ins->setUInt64(5, time(nullptr)); - ins->setString(6, subject.c_str()); - ins->setString(7, body.c_str()); - ins->setUInt(8, 0); - ins->setInt(9, attachment); - ins->setInt(10, 0); - ins->setInt(11, attachmentCount); - ins->execute(); - - delete ins; + Database::Get()->InsertNewMail(mailInsert); if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server @@ -220,43 +217,30 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd } //Get the receiver's id: - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id from charinfo WHERE name=? LIMIT 1;"); - stmt->setString(1, recipient); - sql::ResultSet* res = stmt->executeQuery(); - uint32_t receiverID = 0; + auto receiverID = Database::Get()->GetCharacterInfo(recipient); - if (res->rowsCount() > 0) { - while (res->next()) receiverID = res->getUInt(1); - } else { + if (!receiverID) { Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::RecipientNotFound); - delete stmt; - delete res; return; } - delete stmt; - delete res; - //Check if we have a valid receiver: - if (GeneralUtils::CaseInsensitiveStringCompare(recipient, character->GetName()) || receiverID == character->GetObjectID()) { + if (GeneralUtils::CaseInsensitiveStringCompare(recipient, character->GetName()) || receiverID->id == character->GetID()) { Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::CannotMailSelf); return; } else { - uint64_t currentTime = time(NULL); - sql::PreparedStatement* ins = Database::CreatePreppedStmt("INSERT INTO `mail`(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`) VALUES (?,?,?,?,?,?,?,?,?,?,?,0)"); - ins->setUInt(1, character->GetObjectID()); - ins->setString(2, character->GetName()); - ins->setUInt(3, receiverID); - ins->setString(4, recipient); - ins->setUInt64(5, currentTime); - ins->setString(6, subject); - ins->setString(7, body); - ins->setUInt(8, itemID); - ins->setInt(9, itemLOT); - ins->setInt(10, 0); - ins->setInt(11, attachmentCount); - ins->execute(); - delete ins; + IMail::MailInfo mailInsert; + mailInsert.senderUsername = character->GetName(); + mailInsert.recipient = recipient; + mailInsert.subject = subject; + mailInsert.body = body; + mailInsert.senderId = character->GetID(); + mailInsert.receiverId = receiverID->id; + mailInsert.itemCount = attachmentCount; + mailInsert.itemID = itemID; + mailInsert.itemLOT = itemLOT; + mailInsert.itemSubkey = LWOOBJID_EMPTY; + Database::Get()->InsertNewMail(mailInsert); } Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success); @@ -279,61 +263,49 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd } void Mail::HandleDataRequest(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT * FROM mail WHERE receiver_id=? limit 20;"); - stmt->setUInt(1, player->GetCharacter()->GetObjectID()); - sql::ResultSet* res = stmt->executeQuery(); + auto playerMail = Database::Get()->GetMailForPlayer(player->GetCharacter()->GetID(), 20); RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::MAIL); bitStream.Write(int(MailMessageID::MailData)); bitStream.Write(int(0)); - bitStream.Write(uint16_t(res->rowsCount())); - bitStream.Write(uint16_t(0)); + bitStream.Write(playerMail.size()); + bitStream.Write(0); - if (res->rowsCount() > 0) { - while (res->next()) { - bitStream.Write(res->getUInt64(1)); //MailID + for (const auto& mail : playerMail) { + bitStream.Write(mail.id); //MailID - /*std::u16string subject = GeneralUtils::UTF8ToUTF16(res->getString(7)); - std::u16string body = GeneralUtils::UTF8ToUTF16(res->getString(8)); - std::u16string sender = GeneralUtils::UTF8ToUTF16(res->getString(3)); + WriteStringAsWString(&bitStream, mail.subject.c_str(), 50); //subject + WriteStringAsWString(&bitStream, mail.body.c_str(), 400); //body + WriteStringAsWString(&bitStream, mail.senderUsername.c_str(), 32); //sender - WriteToPacket(&bitStream, subject, 50); - WriteToPacket(&bitStream, body, 400); - WriteToPacket(&bitStream, sender, 32);*/ + bitStream.Write(uint32_t(0)); + bitStream.Write(uint64_t(0)); - WriteStringAsWString(&bitStream, res->getString(7).c_str(), 50); //subject - WriteStringAsWString(&bitStream, res->getString(8).c_str(), 400); //body - WriteStringAsWString(&bitStream, res->getString(3).c_str(), 32); //sender + bitStream.Write(mail.itemID); //Attachment ID + LOT lot = mail.itemLOT; + if (lot <= 0) bitStream.Write(LOT(-1)); + else bitStream.Write(lot); + bitStream.Write(uint32_t(0)); - bitStream.Write(uint32_t(0)); - bitStream.Write(uint64_t(0)); + bitStream.Write(mail.itemSubkey); //Attachment subKey + bitStream.Write(mail.itemCount); //Attachment count - bitStream.Write(res->getUInt64(9)); //Attachment ID - LOT lot = res->getInt(10); - if (lot <= 0) bitStream.Write(LOT(-1)); - else bitStream.Write(lot); - bitStream.Write(uint32_t(0)); + bitStream.Write(uint32_t(0)); + bitStream.Write(uint16_t(0)); - bitStream.Write(res->getInt64(11)); //Attachment subKey - bitStream.Write(uint16_t(res->getInt(12))); //Attachment count + bitStream.Write(mail.timeSent); //time sent (twice?) + bitStream.Write(mail.timeSent); + bitStream.Write(mail.wasRead); //was read - bitStream.Write(uint32_t(0)); - bitStream.Write(uint16_t(0)); - - bitStream.Write(uint64_t(res->getUInt64(6))); //time sent (twice?) - bitStream.Write(uint64_t(res->getUInt64(6))); - bitStream.Write(uint8_t(res->getBoolean(13))); //was read - - bitStream.Write(uint8_t(0)); - bitStream.Write(uint16_t(0)); - bitStream.Write(uint32_t(0)); - } + bitStream.Write(uint8_t(0)); + bitStream.Write(uint16_t(0)); + bitStream.Write(uint32_t(0)); } Game::server->Send(&bitStream, sysAddr, false); - PacketUtils::SavePacket("Max_Mail_Data.bin", (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); + // PacketUtils::SavePacket("Max_Mail_Data.bin", (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); } void Mail::HandleAttachmentCollect(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player) { @@ -345,31 +317,24 @@ void Mail::HandleAttachmentCollect(RakNet::BitStream* packet, const SystemAddres packet->Read(playerID); if (mailID > 0 && playerID == player->GetObjectID()) { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;"); - stmt->setUInt64(1, mailID); - sql::ResultSet* res = stmt->executeQuery(); + auto playerMail = Database::Get()->GetMail(mailID); LOT attachmentLOT = 0; uint32_t attachmentCount = 0; - while (res->next()) { - attachmentLOT = res->getInt(1); - attachmentCount = res->getInt(2); + if (playerMail) { + attachmentLOT = playerMail->itemLOT; + attachmentCount = playerMail->itemCount; } - auto inv = static_cast(player->GetComponent(eReplicaComponentType::INVENTORY)); + auto inv = player->GetComponent(); if (!inv) return; inv->AddItem(attachmentLOT, attachmentCount, eLootSourceType::MAIL); Mail::SendAttachmentRemoveConfirm(sysAddr, mailID); - sql::PreparedStatement* up = Database::CreatePreppedStmt("UPDATE mail SET attachment_lot=0 WHERE id=?;"); - up->setUInt64(1, mailID); - up->execute(); - delete up; - delete res; - delete stmt; + Database::Get()->ClaimMailItem(mailID); } } @@ -394,15 +359,9 @@ void Mail::HandleMailRead(RakNet::BitStream* packet, const SystemAddress& sysAdd } void Mail::HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID) { - auto returnVal = std::async(std::launch::async, [&]() { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id FROM mail WHERE receiver_id=? AND was_read=0"); - stmt->setUInt(1, objectID); - sql::ResultSet* res = stmt->executeQuery(); + auto unreadMailCount = Database::Get()->GetUnreadMailCount(objectID); - if (res->rowsCount() > 0) Mail::SendNotification(sysAddr, res->rowsCount()); - delete res; - delete stmt; - }); + if (unreadMailCount > 0) Mail::SendNotification(sysAddr, unreadMailCount); } void Mail::SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response) { @@ -449,10 +408,7 @@ void Mail::SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOO bitStream.Write(mailID); Game::server->Send(&bitStream, sysAddr, false); - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM mail WHERE id=? LIMIT 1;"); - stmt->setUInt64(1, mailID); - stmt->execute(); - delete stmt; + Database::Get()->DeleteMail(mailID); } void Mail::SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID) { @@ -463,8 +419,5 @@ void Mail::SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID) { bitStream.Write(mailID); Game::server->Send(&bitStream, sysAddr, false); - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE mail SET was_read=1 WHERE id=?"); - stmt->setUInt64(1, mailID); - stmt->execute(); - delete stmt; + Database::Get()->MarkMailRead(mailID); } diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index db27ee7b..71f0e38b 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::Get()->InsertSlashCommandUsage(entity->GetObjectID(), chatCommand); if (chatCommand == "setminifig" && args.size() == 2 && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { // could break characters so only allow if GM > 0 int32_t minifigItemId; @@ -816,46 +812,36 @@ 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(); + auto playerInfo = Database::Get()->GetCharacterInfo(playerName); + uint32_t receiverID = 0; - - if (res->rowsCount() > 0) { - while (res->next()) receiverID = res->getUInt(1); - } - - delete stmt; - delete res; - - if (receiverID == 0) { + if (!playerInfo) { ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); return; } - uint32_t lot; + receiverID = playerInfo->id; + + LOT lot; if (!GeneralUtils::TryParse(args[1], lot)) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); return; } - uint64_t currentTime = time(NULL); - sql::PreparedStatement* ins = Database::CreatePreppedStmt("INSERT INTO `mail`(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`) VALUES (?,?,?,?,?,?,?,?,?,?,?,0)"); - ins->setUInt(1, entity->GetObjectID()); - ins->setString(2, "Darkflame Universe"); - ins->setUInt(3, receiverID); - ins->setString(4, playerName); - ins->setUInt64(5, currentTime); - ins->setString(6, "Lost item"); - ins->setString(7, "This is a replacement item for one you lost."); - ins->setUInt(8, 0); - ins->setInt(9, lot); - ins->setInt(10, 0); - ins->setInt(11, 1); - ins->execute(); - delete ins; + IMail::MailInfo mailInsert; + mailInsert.senderId = entity->GetObjectID(); + mailInsert.senderUsername = "Darkflame Universe"; + mailInsert.receiverId = receiverID; + mailInsert.recipient = playerName; + mailInsert.subject = "Lost item"; + mailInsert.body = "This is a replacement item for one you lost."; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = lot; + mailInsert.itemSubkey = LWOOBJID_EMPTY; + mailInsert.itemCount = 1; + Database::Get()->InsertNewMail(mailInsert); ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); @@ -1015,25 +1001,16 @@ 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 characterInfo = Database::Get()->GetCharacterInfo(args[0]); - accountQuery->setString(1, args[0]); + if (characterInfo) { + accountId = characterInfo->accountId; + characterId = characterInfo->id; - 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); - } + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); } - delete accountQuery; - delete result; - if (accountId == 0) { ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); @@ -1044,8 +1021,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit characterId = player->GetObjectID(); } - auto* userUpdate = Database::CreatePreppedStmt("UPDATE accounts SET mute_expire = ? WHERE id = ?;"); - time_t expire = 1; // Default to indefinate mute if (args.size() >= 2) { @@ -1070,12 +1045,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit expire += 60 * 60 * hours; } - userUpdate->setUInt64(1, expire); - userUpdate->setInt(2, accountId); - - userUpdate->executeUpdate(); - - delete userUpdate; + Database::Get()->UpdateAccountUnmuteTime(accountId, expire); char buffer[32] = "brought up for review.\0"; @@ -1127,19 +1097,12 @@ 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 characterInfo = Database::Get()->GetCharacterInfo(args[0]); - accountQuery->setString(1, args[0]); - - auto result = accountQuery->executeQuery(); - - if (result->rowsCount() > 0) { - while (result->next()) accountId = result->getUInt(1); + if (characterInfo) { + accountId = characterInfo->accountId; } - delete accountQuery; - delete result; - if (accountId == 0) { ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); @@ -1149,13 +1112,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit accountId = player->GetParentUser()->GetAccountID(); } - auto* userUpdate = Database::CreatePreppedStmt("UPDATE accounts SET banned = true WHERE id = ?;"); - - userUpdate->setInt(1, accountId); - - userUpdate->executeUpdate(); - - delete userUpdate; + Database::Get()->UpdateAccountBan(accountId, true); if (player != nullptr) { Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 72eb4e59..e5947009 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -130,13 +130,8 @@ int main(int argc, char** argv) { LOG("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); + Database::Connect(); } catch (sql::SQLException& ex) { LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Migrations not run"); @@ -225,19 +220,14 @@ int main(int argc, char** argv) { std::cout << "Enter a username: "; std::cin >> username; - std::unique_ptr userLookupStatement(Database::CreatePreppedStmt("SELECT id FROM accounts WHERE name=? LIMIT 1;")); - userLookupStatement->setString(1, username.c_str()); - std::unique_ptr res(userLookupStatement->executeQuery()); - if (res->rowsCount() > 0) { + auto accountId = Database::Get()->GetAccountInfo(username); + if (accountId) { LOG("Account with name \"%s\" already exists", username.c_str()); std::cout << "Do you want to change the password of that account? [y/n]?"; std::string prompt = ""; std::cin >> prompt; if (prompt == "y" || prompt == "yes") { - uint32_t accountId = 0; - res->next(); - accountId = res->getUInt(1); - if (accountId == 0) return EXIT_FAILURE; + if (accountId->id == 0) return EXIT_FAILURE; //Read the password from the console without echoing it. #ifdef __linux__ @@ -257,10 +247,7 @@ int main(int argc, char** argv) { bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash); assert(bcryptState == 0); - std::unique_ptr userUpdateStatement(Database::CreatePreppedStmt("UPDATE accounts SET password = ? WHERE id = ?;")); - userUpdateStatement->setString(1, std::string(hash, BCRYPT_HASHSIZE).c_str()); - userUpdateStatement->setUInt(2, accountId); - userUpdateStatement->execute(); + Database::Get()->UpdateAccountPassword(accountId->id, std::string(hash, BCRYPT_HASHSIZE)); LOG("Account \"%s\" password updated successfully!", username.c_str()); } else { @@ -289,11 +276,7 @@ int main(int argc, char** argv) { //Create account try { - std::unique_ptr statement(Database::CreatePreppedStmt("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);")); - statement->setString(1, username.c_str()); - statement->setString(2, std::string(hash, BCRYPT_HASHSIZE).c_str()); - statement->setInt(3, 9); - statement->execute(); + Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE)); } catch(sql::SQLException& e) { LOG("A SQL error occurred!:\n %s", e.what()); return EXIT_FAILURE; @@ -312,8 +295,6 @@ 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 master_server_ip = Game::config->GetValue("master_ip"); @@ -321,22 +302,7 @@ int main(int argc, char** argv) { master_server_ip = Game::server->GetIP(); } - //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; - } 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; - } + Database::Get()->SetMasterIp(master_server_ip, Game::server->GetPort()); //Create additional objects here: ObjectIDManager::Instance()->Initialize(Game::logger); @@ -384,16 +350,12 @@ int main(int argc, char** argv) { //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); + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; } - delete res; - delete stmt; - framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; diff --git a/dMasterServer/ObjectIDManager.cpp b/dMasterServer/ObjectIDManager.cpp index df0613cc..4739addf 100644 --- a/dMasterServer/ObjectIDManager.cpp +++ b/dMasterServer/ObjectIDManager.cpp @@ -14,58 +14,35 @@ void ObjectIDManager::Initialize(Logger* 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; + auto lastObjectId = Database::Get()->GetCurrentPersistentId(); + if (!lastObjectId) { + Database::Get()->InsertDefaultPersistentId(); return; + } else { + this->currentPersistentID = lastObjectId.value(); } - while (next) { - this->currentPersistentID = - result->getInt(1) > 0 ? result->getInt(1) : 1; - next = result->next(); + if (this->currentPersistentID <= 0) { + LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation."); + throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation."); } - - delete result; - delete stmt; } catch (sql::SQLException& e) { - LOG("Unable to fetch max persistent object ID in use. Defaulting to 1."); + LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions."); LOG("SQL error: %s", e.what()); - this->currentPersistentID = 1; + throw; } } //! Generates a new persistent ID -uint32_t ObjectIDManager::GeneratePersistentID(void) { +uint32_t ObjectIDManager::GeneratePersistentID() { 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; - // } + SaveToDatabase(); 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::Get()->UpdatePersistentId(this->currentPersistentID); } diff --git a/dMasterServer/ObjectIDManager.h b/dMasterServer/ObjectIDManager.h index 1454d08f..cdb6dcdf 100644 --- a/dMasterServer/ObjectIDManager.h +++ b/dMasterServer/ObjectIDManager.h @@ -41,7 +41,7 @@ public: /*! \return The new persistent ID */ - uint32_t GeneratePersistentID(void); + uint32_t GeneratePersistentID(); void SaveToDatabase(); }; diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 1445be37..12f140d6 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -27,6 +27,7 @@ #include "eConnectionType.h" #include "eServerMessageType.h" #include "eMasterMessageType.h" +#include "eGameMasterLevel.h" void AuthPackets::HandleHandshake(dServer* server, Packet* packet) { RakNet::BitStream inStream(packet->data, packet->length, false); @@ -64,120 +65,54 @@ 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 accountInfo = Database::Get()->GetAccountInfo(username); - sql::ResultSet* res = stmt->executeQuery(); - - if (res->rowsCount() == 0) { - LOG("No user found!"); + if (!accountInfo) { + LOG("No user by name %s found!", username.c_str()); 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)) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { 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") { + if (Game::config->GetValue("dont_use_keys") != "1" && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { + LOG(""); //Check to see if we have a play key: - if (sqlPlayKey == 0 && sqlGmLevel == 0) { + if (accountInfo->playKeyId == 0) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username); LOG("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; + auto playKeyStatus = Database::Get()->IsPlaykeyActive(accountInfo->playKeyId); - if (keyRes->rowsCount() == 0 && sqlGmLevel == 0) { - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username); + if (!playKeyStatus) { + AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username); return; } - while (keyRes->next()) { - isKeyActive = (bool)keyRes->getInt(1); - } - - if (!isKeyActive && sqlGmLevel == 0) { + if (!playKeyStatus.value()) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username); LOG("User %s tried to log in, but their play key was disabled", username.c_str()); return; } } - if (sqlBanned) { + if (accountInfo->banned) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, "", "", 2001, username); return; } - if (sqlLocked) { + if (accountInfo->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(); - } - } else { - // Login success with bcrypt - } + bool loginSuccess = ::bcrypt_checkpw(password.c_str(), accountInfo->bcryptPassword.c_str()) == 0; if (!loginSuccess) { AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username); diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index daf0221a..18b2f3d1 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -353,30 +353,18 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa LWOOBJID idOfReceiver = LWOOBJID_EMPTY; { - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE name = ?"); - stmt->setString(1, receiver); + auto characterIdFetch = Database::Get()->GetCharacterInfo(receiver); - sql::ResultSet* res = stmt->executeQuery(); - - if (res->next()) { - idOfReceiver = res->getInt("id"); + if (characterIdFetch) { + idOfReceiver = characterIdFetch->id; } - - delete stmt; - delete res; } 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()); + auto bffInfo = Database::Get()->GetBestFriendStatus(entity->GetObjectID(), idOfReceiver); - sql::ResultSet* res = stmt->executeQuery(); - - if (res->next()) { - isBestFriend = res->getInt("best_friend") == 3; + if (bffInfo) { + isBestFriend = bffInfo->bestFriendStatus == 3; } if (isBestFriend) { @@ -384,9 +372,6 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa 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 e8f5915d..cfd7d157 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -74,6 +74,7 @@ #include "ZCompression.h" #include "EntityManager.h" #include "CheatDetection.h" +#include "eGameMasterLevel.h" namespace Game { Logger* logger = nullptr; @@ -180,20 +181,15 @@ 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")); } + //Connect to the MySQL Database: try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); + Database::Connect(); } catch (sql::SQLException& ex) { LOG("Got an error while connecting to the database: %s", ex.what()); return EXIT_FAILURE; @@ -202,15 +198,12 @@ int main(int argc, char** argv) { //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); - } + auto masterInfo = Database::Get()->GetMasterInfo(); - delete res; - delete stmt; + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; + } ObjectIDManager::Instance()->Initialize(); UserManager::Instance()->Initialize(); @@ -485,16 +478,12 @@ int main(int argc, char** argv) { //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); + auto masterInfo = Database::Get()->GetMasterInfo(); + if (masterInfo) { + masterIP = masterInfo->ip; + masterPort = masterInfo->port; } - delete res; - delete stmt; - framesSinceLastSQLPing = 0; } else framesSinceLastSQLPing++; @@ -886,20 +875,15 @@ 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* res = stmt->executeQuery(); - while (res->next()) { - gmLevel = res->getInt(1); + auto accountInfo = Database::Get()->GetAccountInfo(username); + if (!accountInfo) { + LOG("Client's account does not exist in the database, aborting connection."); + Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::CHARACTER_NOT_FOUND); + return; } - delete stmt; - delete res; - // Developers may skip this check - if (gmLevel < 8 && clientDatabaseChecksum != databaseChecksum) { + if (accountInfo->maxGmLevel < eGameMasterLevel::DEVELOPER && clientDatabaseChecksum != databaseChecksum) { LOG("Client's database checksum does not match the server's, aborting connection."); Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::WRONG_GAME_VERSION); return; @@ -963,7 +947,7 @@ void HandlePacket(Packet* packet) { CheckType::Entity, "Sending GM with a sending player that does not match their own. GM ID: %i", static_cast(messageID) - ); + ); if (isSender) GameMessageHandler::HandleMessage(&dataStream, packet->systemAddress, objectID, messageID); break; @@ -987,7 +971,7 @@ void HandlePacket(Packet* packet) { CheckType::User, "Sending login request with a sending player that does not match their own. Player ID: %llu", playerID - ); + ); if (!valid) return; @@ -1058,23 +1042,23 @@ void HandlePacket(Packet* packet) { if (!levelComponent) return; auto version = levelComponent->GetCharacterVersion(); - switch(version) { - case eCharacterVersion::RELEASE: - // TODO: Implement, super low priority - case eCharacterVersion::LIVE: - LOG("Updating Character Flags"); - c->SetRetroactiveFlags(); - levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); - case eCharacterVersion::PLAYER_FACTION_FLAGS: - LOG("Updating Vault Size"); - player->RetroactiveVaultSize(); - levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); - case eCharacterVersion::VAULT_SIZE: - LOG("Updaing Speedbase"); - levelComponent->SetRetroactiveBaseSpeed(); - levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); - case eCharacterVersion::UP_TO_DATE: - break; + switch (version) { + case eCharacterVersion::RELEASE: + // TODO: Implement, super low priority + case eCharacterVersion::LIVE: + LOG("Updating Character Flags"); + c->SetRetroactiveFlags(); + levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); + case eCharacterVersion::PLAYER_FACTION_FLAGS: + LOG("Updating Vault Size"); + player->RetroactiveVaultSize(); + levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); + case eCharacterVersion::VAULT_SIZE: + LOG("Updaing Speedbase"); + levelComponent->SetRetroactiveBaseSpeed(); + levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); + case eCharacterVersion::UP_TO_DATE: + break; } player->GetCharacter()->SetTargetScene(""); @@ -1090,89 +1074,45 @@ void HandlePacket(Packet* packet) { if (g_CloneID != 0) { const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); - const auto zoneId = Game::server->GetZoneID(); + const auto zoneId = worldId.GetMapID(); const auto cloneId = g_CloneID; - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT id FROM PropertyTemplate WHERE mapID = ?;"); - query.bind(1, (int)zoneId); + //Check for BBB models: + auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); - auto result = query.execQuery(); - - if (result.eof() || result.fieldIsNull(0)) { - LOG("No property templates found for zone %d, not sending BBB", zoneId); + LWOOBJID propertyId = LWOOBJID_EMPTY; + if (propertyInfo) propertyId = propertyInfo->id; + else { + LOG("Couldn't find property ID for zone %i, clone %i", zoneId, cloneId); goto noBBB; } + for (auto& bbbModel : Database::Get()->GetUgcModels(propertyId)) { + LOG("Getting lxfml ugcID: %llu", bbbModel.id); - //Check for BBB models: - auto stmt = Database::CreatePreppedStmt("SELECT ugc_id FROM properties_contents WHERE lot=14 AND property_id=?"); + bbbModel.lxfmlData.seekg(0, std::ios::end); + size_t lxfmlSize = bbbModel.lxfmlData.tellg(); + bbbModel.lxfmlData.seekg(0); - int32_t templateId = result.getIntField(0); + //Send message: + LWOOBJID blueprintID = bbbModel.id; + GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); + GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); - result.finalize(); + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(1); + bitStream.Write(blueprintID); - auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;"); + bitStream.Write(lxfmlSize); - propertyLookup->setInt(1, templateId); - propertyLookup->setInt64(2, g_CloneID); + bitStream.WriteAlignedBytes(reinterpret_cast(bbbModel.lxfmlData.str().c_str()), lxfmlSize); - auto* propertyEntry = propertyLookup->executeQuery(); - uint64_t propertyId = 0; - - if (propertyEntry->next()) { - propertyId = propertyEntry->getUInt64(1); + SystemAddress sysAddr = packet->systemAddress; + SEND_PACKET; + // PacketUtils::SavePacket("lxfml packet " + std::to_string(bbbModel.id) + ".bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); } - - delete propertyLookup; - - stmt->setUInt64(1, propertyId); - auto res = stmt->executeQuery(); - while (res->next()) { - LOG("Getting lxfml ugcID: %u", res->getUInt(1)); - - //Get lxfml: - auto stmtL = Database::CreatePreppedStmt("SELECT lxfml from ugc where id=?"); - stmtL->setUInt(1, res->getUInt(1)); - - auto lxres = stmtL->executeQuery(); - - while (lxres->next()) { - auto lxfml = lxres->getBlob(1); - - lxfml->seekg(0, std::ios::end); - size_t lxfmlSize = lxfml->tellg(); - lxfml->seekg(0); - - //Send message: - { - LWOOBJID blueprintID = res->getUInt(1); - GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); - - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::BLUEPRINT_SAVE_RESPONSE); - bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes - bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); - bitStream.Write(1); - bitStream.Write(blueprintID); - - bitStream.Write(lxfmlSize); - - for (size_t i = 0; i < lxfmlSize; ++i) - bitStream.Write(lxfml->get()); - - SystemAddress sysAddr = packet->systemAddress; - SEND_PACKET; - PacketUtils::SavePacket("lxfml packet " + std::to_string(res->getUInt(1)) + ".bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); - } - } - - delete stmtL; - delete lxres; - } - - delete stmt; - delete res; } noBBB: @@ -1330,6 +1270,7 @@ void WorldShutdownProcess(uint32_t zoneId) { if (PropertyManagementComponent::Instance() != nullptr) { LOG("Saving ALL property data for zone %i clone %i!", zoneId, PropertyManagementComponent::Instance()->GetCloneId()); PropertyManagementComponent::Instance()->Save(); + Database::Get()->RemoveUnreferencedUgcModels(); LOG("ALL property data saved for zone %i clone %i!", zoneId, PropertyManagementComponent::Instance()->GetCloneId()); } diff --git a/resources/chatconfig.ini b/resources/chatconfig.ini index 26b26cc7..1bb1d1b2 100644 --- a/resources/chatconfig.ini +++ b/resources/chatconfig.ini @@ -1,2 +1,11 @@ # Port number port=2005 + +# If you would like to increase the maximum number of best friends a player can have on the server +# Change the value below to what you would like this to be (5 is live accurate) +max_number_of_best_friends=5 + +# If you would like to increase the maximum number of friends a player can have on the server +# Change the value below to what you would like this to be (50 is live accurate) +# going over 50 will be allowed in some secnarios, but proper handling will require client modding +max_number_of_friends=50 diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 0072fc51..0b15973d 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -40,10 +40,6 @@ classic_survival_scoring=0 # If this value is 1, pets will consume imagination as they did in live. if 0 they will not consume imagination at all. pets_take_imagination=1 -# If you would like to increase the maximum number of best friends a player can have on the server -# Change the value below to what you would like this to be (5 is live accurate) -max_number_of_best_friends=5 - # Disables loot drops disable_drops=0