From a575101751e968ce5f9120cdda832fbbbd02d610 Mon Sep 17 00:00:00 2001 From: Yehonal Date: Wed, 3 Sep 2025 03:31:09 +0200 Subject: [PATCH] feat(core): implement 31-bit safe petition_id for improved database integrity (#22774) --- .../rev_1756841669142726711.sql | 14 ++++ .../Implementation/CharacterDatabase.cpp | 10 +-- .../Implementation/CharacterDatabase.h | 4 +- src/server/game/Handlers/PetitionsHandler.cpp | 36 +++++---- src/server/game/Petitions/PetitionMgr.cpp | 77 +++++++++++++++++-- src/server/game/Petitions/PetitionMgr.h | 19 ++++- 6 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 data/sql/updates/pending_db_characters/rev_1756841669142726711.sql diff --git a/data/sql/updates/pending_db_characters/rev_1756841669142726711.sql b/data/sql/updates/pending_db_characters/rev_1756841669142726711.sql new file mode 100644 index 000000000..42fb73711 --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_1756841669142726711.sql @@ -0,0 +1,14 @@ +-- Add petition_id column to petition table +ALTER TABLE `petition` ADD COLUMN `petition_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `petitionguid`; +-- Populate petition_id based on petitionguid +UPDATE `petition` SET `petition_id` = CASE WHEN `petitionguid` <= 2147483647 THEN `petitionguid` ELSE `petitionguid` - 2147483648 END WHERE `petition_id` = 0; +-- Add index on petition_id +ALTER TABLE `petition` ADD INDEX `idx_petition_id` (`petition_id`); +-- Add petition_id column to petition_sign table +ALTER TABLE `petition_sign` ADD COLUMN `petition_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `petitionguid`; +-- Populate petition_id in petition_sign from petition table +UPDATE `petition_sign` AS `ps` JOIN `petition` AS `p` ON `p`.`petitionguid` = `ps`.`petitionguid` SET `ps`.`petition_id` = `p`.`petition_id` WHERE `ps`.`petition_id` = 0; +-- Add index on petition_id and playerguid in petition_sign +ALTER TABLE `petition_sign` ADD INDEX `idx_petition_id_player` (`petition_id`, `playerguid`); +-- Update enchantments in item_instance with petition_id prefix +UPDATE `item_instance` AS `ii` JOIN `petition` AS `p` ON `p`.`petitionguid` = `ii`.`guid` SET `ii`.`enchantments` = CONCAT(`p`.`petition_id`, SUBSTRING(`ii`.`enchantments`, LOCATE(' ', `ii`.`enchantments`))) WHERE `ii`.`enchantments` IS NOT NULL AND `ii`.`enchantments` <> ''; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index ec080b784..27a8b8940 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -353,8 +353,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_REM_AT_LOGIN_FLAG, "UPDATE characters set at_login = at_login & ~ ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ALL_AT_LOGIN_FLAGS, "UPDATE characters SET at_login = at_login | ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_BUG_REPORT, "INSERT INTO bugreport (type, content) VALUES(?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petitionguid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petitionguid, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petition_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petition_id, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ACCOUNT_ONLINE, "UPDATE characters SET online = 0 WHERE account = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_GROUP, "INSERT INTO `groups` (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_REP_GROUP_MEMBER, "REPLACE INTO group_member (guid, memberGuid, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC); @@ -456,9 +456,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_GUID, "DELETE FROM petition_sign WHERE petitionguid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (petition_id, ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_PETITION_BY_ID, "DELETE FROM petition WHERE petition_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_ID, "DELETE FROM petition_sign WHERE petition_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_DECLINED_NAME, "DELETE FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_DECLINED_NAME, "INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_RACE, "UPDATE characters SET race = ? WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index e7e05e473..b378134c3 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -381,8 +381,8 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_INSTANCE_BY_INSTANCE, CHAR_DEL_MAIL_ITEM_BY_ID, CHAR_INS_PETITION, - CHAR_DEL_PETITION_BY_GUID, - CHAR_DEL_PETITION_SIGNATURE_BY_GUID, + CHAR_DEL_PETITION_BY_ID, + CHAR_DEL_PETITION_SIGNATURE_BY_ID, CHAR_DEL_CHAR_DECLINED_NAME, CHAR_INS_CHAR_DECLINED_NAME, CHAR_UPD_CHAR_RACE, diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index 796144a13..7ff2091d4 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -183,7 +183,9 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) if (!charter) return; - charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1, charter->GetGUID().GetCounter()); + // Use a 31-bit safe petition id instead of the raw item guid + uint32 petitionId = sPetitionMgr->GeneratePetitionId(); + charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1, petitionId); // ITEM_FIELD_ENCHANTMENT_1_1 is guild/arenateam id // ITEM_FIELD_ENCHANTMENT_1_1+1 is current signatures count (showed on item) charter->SetState(ITEM_CHANGED, _player); @@ -211,16 +213,18 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) // xinef: petition pointer is invalid from now on CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PETITION); - stmt->SetData(0, _player->GetGUID().GetCounter()); - stmt->SetData(1, charter->GetGUID().GetCounter()); - stmt->SetData(2, name); - stmt->SetData(3, uint8(type)); + // petition_id, ownerguid, petitionguid(item guid), name, type + stmt->SetData(0, petitionId); + stmt->SetData(1, _player->GetGUID().GetCounter()); + stmt->SetData(2, charter->GetGUID().GetCounter()); + stmt->SetData(3, name); + stmt->SetData(4, uint8(type)); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); - // xinef: fill petition store - sPetitionMgr->AddPetition(charter->GetGUID(), _player->GetGUID(), name, uint8(type)); + // xinef: fill petition store (include petitionId) + sPetitionMgr->AddPetition(charter->GetGUID(), _player->GetGUID(), name, uint8(type), petitionId); } void WorldSession::HandlePetitionShowSignOpcode(WorldPacket& recvData) @@ -249,7 +253,7 @@ void WorldSession::HandlePetitionShowSignOpcode(WorldPacket& recvData) WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8 + 8 + 4 + 1 + signs * 12)); data << petitionguid; // petition guid data << _player->GetGUID(); // owner guid - data << uint32(petitionguid.GetCounter()); // guild guid + data << uint32(petition->petitionId); // guild/team id (31-bit safe) data << uint8(signs); // sign's count if (signs) @@ -286,7 +290,7 @@ void WorldSession::SendPetitionQueryOpcode(ObjectGuid petitionguid) uint8 type = petition->petitionType; WorldPacket data(SMSG_PETITION_QUERY_RESPONSE, (4 + 8 + petition->petitionName.size() + 1 + 1 + 4 * 12 + 2 + 10)); - data << uint32(petitionguid.GetCounter()); // guild/team guid (in Trinity always same as petition low guid + data << uint32(petition->petitionId); // guild/team id (was item low guid) data << petition->ownerGuid; // charter owner guid data << petition->petitionName; // name (guild/arena team) data << uint8(0); // some string @@ -373,7 +377,7 @@ void WorldSession::HandlePetitionRenameOpcode(WorldPacket& recvData) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PETITION_NAME); stmt->SetData(0, newName); - stmt->SetData(1, petitionGuid.GetCounter()); + stmt->SetData(1, petition->petitionId); CharacterDatabase.Execute(stmt); @@ -497,7 +501,7 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PETITION_SIGNATURE); stmt->SetData(0, petition->ownerGuid.GetCounter()); - stmt->SetData(1, petitionGuid.GetCounter()); + stmt->SetData(1, petition->petitionId); stmt->SetData(2, playerGuid.GetCounter()); stmt->SetData(3, GetAccountId()); @@ -625,7 +629,7 @@ void WorldSession::HandleOfferPetitionOpcode(WorldPacket& recvData) WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8 + 8 + 4 + signs + signs * 12)); data << petitionguid; // petition guid data << _player->GetGUID(); // owner guid - data << uint32(petitionguid.GetCounter()); // guild guid + data << uint32(petition->petitionId); // guild/team id (31-bit safe) data << uint8(signs); // sign's count if (signs) @@ -790,12 +794,12 @@ void WorldSession::HandleTurnInPetitionOpcode(WorldPacket& recvData) CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_GUID); - stmt->SetData(0, petitionGuid.GetCounter()); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_ID); + stmt->SetData(0, petition->petitionId); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_GUID); - stmt->SetData(0, petitionGuid.GetCounter()); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_ID); + stmt->SetData(0, petition->petitionId); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); diff --git a/src/server/game/Petitions/PetitionMgr.cpp b/src/server/game/Petitions/PetitionMgr.cpp index a3c5c69ff..7e946923e 100644 --- a/src/server/game/Petitions/PetitionMgr.cpp +++ b/src/server/game/Petitions/PetitionMgr.cpp @@ -41,8 +41,9 @@ void PetitionMgr::LoadPetitions() { uint32 oldMSTime = getMSTime(); PetitionStore.clear(); + PetitionIdToItemGuid.clear(); - QueryResult result = CharacterDatabase.Query("SELECT ownerguid, petitionguid, name, type FROM petition"); + QueryResult result = CharacterDatabase.Query("SELECT ownerguid, petitionguid, name, type, petition_id FROM petition"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Petitions!"); @@ -51,13 +52,24 @@ void PetitionMgr::LoadPetitions() } uint32 count = 0; + uint32 maxId = 0; do { Field* fields = result->Fetch(); - AddPetition(ObjectGuid::Create(fields[1].Get()), ObjectGuid::Create(fields[0].Get()), fields[2].Get(), fields[3].Get()); + uint32 itemLow = fields[1].Get(); + uint32 petitionId = fields[4].Get(); + ObjectGuid itemGuid = ObjectGuid::Create(itemLow); + ObjectGuid ownerGuid = ObjectGuid::Create(fields[0].Get()); + AddPetition(itemGuid, ownerGuid, fields[2].Get(), fields[3].Get(), petitionId); + PetitionIdToItemGuid[petitionId] = itemGuid; + if (petitionId > maxId) + maxId = petitionId; ++count; } while (result->NextRow()); + // initialize next id (keep within 31-bit safe range) + _nextPetitionId = std::min(std::max(maxId + 1, 1), 0x7FFFFFFFu); + LOG_INFO("server.loading", ">> Loaded {} Petitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } @@ -67,7 +79,7 @@ void PetitionMgr::LoadSignatures() uint32 oldMSTime = getMSTime(); SignatureStore.clear(); - QueryResult result = CharacterDatabase.Query("SELECT petitionguid, playerguid, player_account FROM petition_sign"); + QueryResult result = CharacterDatabase.Query("SELECT petition_id, playerguid, player_account FROM petition_sign"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Petition signs!"); @@ -79,7 +91,11 @@ void PetitionMgr::LoadSignatures() do { Field* fields = result->Fetch(); - AddSignature(ObjectGuid::Create(fields[0].Get()), fields[2].Get(), ObjectGuid::Create(fields[1].Get())); + uint32 petitionId = fields[0].Get(); + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + continue; // orphan signature, skip + AddSignature(it->second, fields[2].Get(), ObjectGuid::Create(fields[1].Get())); ++count; } while (result->NextRow()); @@ -87,10 +103,11 @@ void PetitionMgr::LoadSignatures() LOG_INFO("server.loading", " "); } -void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type) +void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type, uint32 petitionId) { Petition& p = PetitionStore[petitionGUID]; p.petitionGuid = petitionGUID; + p.petitionId = petitionId; p.ownerGuid = ownerGuid; p.petitionName = name; p.petitionType = type; @@ -102,7 +119,12 @@ void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std void PetitionMgr::RemovePetition(ObjectGuid petitionGUID) { - PetitionStore.erase(petitionGUID); + auto it = PetitionStore.find(petitionGUID); + if (it != PetitionStore.end()) + { + PetitionIdToItemGuid.erase(it->second.petitionId); + PetitionStore.erase(it); + } // remove signatures SignatureStore.erase(petitionGUID); @@ -143,6 +165,15 @@ Petition const* PetitionMgr::GetPetition(ObjectGuid petitionGUID) const return nullptr; } +Petition const* PetitionMgr::GetPetitionById(uint32 petitionId) const +{ + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + return nullptr; + + return GetPetition(it->second); +} + Petition const* PetitionMgr::GetPetitionByOwnerWithType(ObjectGuid ownerGuid, uint8 type) const { for (PetitionContainer::const_iterator itr = PetitionStore.begin(); itr != PetitionStore.end(); ++itr) @@ -189,3 +220,37 @@ void PetitionMgr::RemoveSignaturesByPlayerAndType(ObjectGuid playerGuid, uint8 t itr->second.signatureMap.erase(signItr); } } + +uint32 PetitionMgr::GeneratePetitionId() +{ + // ensure 31-bit range and avoid collisions with already loaded petitions + if (_nextPetitionId == 0 || _nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + + // find first free id + while (PetitionIdToItemGuid.count(_nextPetitionId)) + { + ++_nextPetitionId; + if (_nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + } + + uint32 id = _nextPetitionId++; + if (_nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + return id; +} + +uint32 PetitionMgr::GetPetitionIdByItemGuid(ObjectGuid petitionItemGuid) const +{ + Petition const* p = GetPetition(petitionItemGuid); + return p ? p->petitionId : 0; +} + +ObjectGuid PetitionMgr::GetItemGuidByPetitionId(uint32 petitionId) const +{ + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + return ObjectGuid::Empty; + return it->second; +} diff --git a/src/server/game/Petitions/PetitionMgr.h b/src/server/game/Petitions/PetitionMgr.h index 97491fb3a..cd7d47ead 100644 --- a/src/server/game/Petitions/PetitionMgr.h +++ b/src/server/game/Petitions/PetitionMgr.h @@ -20,6 +20,7 @@ #include "ObjectGuid.h" +#include #include #define CHARTER_DISPLAY_ID 16161 @@ -37,14 +38,21 @@ typedef std::map SignatureMap; struct Petition { + // Item GUID of the charter item (used to find the item in inventory) ObjectGuid petitionGuid; + // New 31-bit safe petition identifier used in packets/DB relations + uint32 petitionId; + // Owner character GUID ObjectGuid ownerGuid; + // Petition type (guild / arena) uint8 petitionType; + // Name associated with the petition (guild/arena name) std::string petitionName; }; struct Signatures { + // Keep keying by item-guid for backward compatibility in code paths ObjectGuid petitionGuid; SignatureMap signatureMap; }; @@ -65,10 +73,11 @@ public: void LoadSignatures(); // Petitions - void AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type); + void AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type, uint32 petitionId); void RemovePetition(ObjectGuid petitionGUID); void RemovePetitionByOwnerAndType(ObjectGuid ownerGuid, uint8 type); Petition const* GetPetition(ObjectGuid petitionGUID) const; + Petition const* GetPetitionById(uint32 petitionId) const; Petition const* GetPetitionByOwnerWithType(ObjectGuid ownerGuid, uint8 type) const; PetitionContainer* GetPetitionStore() { return &PetitionStore; } @@ -79,9 +88,17 @@ public: Signatures const* GetSignature(ObjectGuid petitionGUID) const; SignatureContainer* GetSignatureStore() { return &SignatureStore; } + uint32 GeneratePetitionId(); + uint32 GetPetitionIdByItemGuid(ObjectGuid petitionItemGuid) const; + ObjectGuid GetItemGuidByPetitionId(uint32 petitionId) const; + protected: PetitionContainer PetitionStore; SignatureContainer SignatureStore; + // Mapping id -> item-guid to support DB-id lookups + std::unordered_map PetitionIdToItemGuid; + // Next petition id (kept < 2^31) + uint32 _nextPetitionId = 1; }; #define sPetitionMgr PetitionMgr::instance()