[11704] Resolve possible crash in auction code for paiment pending state.

Source of crash in sharing item object for sent mail to new owner
and in same time use it in still existed auction for show pending paiment.
Crash possible if new onwer get mail in less hour delay and will drop item
at receive.

Solution: Added fields in memory auction object and table `auction` for
store item stack size and random property id. This let not use auction item
except points where item send to owner at expire and new owner at
buyout/timeout auction with bid.
This commit is contained in:
VladimirMangos 2011-07-02 03:31:09 +04:00
parent 0daf12a348
commit 0f0fa22607
7 changed files with 129 additions and 103 deletions

View file

@ -21,7 +21,7 @@
DROP TABLE IF EXISTS `character_db_version`;
CREATE TABLE `character_db_version` (
`required_11620_01_characters_character_equipmentsets` bit(1) default NULL
`required_11704_01_characters_auction` bit(1) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Last applied sql update to DB';
--
@ -144,6 +144,8 @@ CREATE TABLE `auction` (
`houseid` int(11) unsigned NOT NULL default '0',
`itemguid` int(11) unsigned NOT NULL default '0',
`item_template` int(11) unsigned NOT NULL default '0' COMMENT 'Item Identifier',
`item_count` int(11) unsigned NOT NULL default '0',
`item_randompropertyid` int(11) NOT NULL default '0',
`itemowner` int(11) unsigned NOT NULL default '0',
`buyoutprice` int(11) NOT NULL default '0',
`time` bigint(40) NOT NULL default '0',
@ -152,8 +154,7 @@ CREATE TABLE `auction` (
`lastbid` int(11) NOT NULL default '0',
`startbid` int(11) NOT NULL default '0',
`deposit` int(11) NOT NULL default '0',
PRIMARY KEY (`id`),
UNIQUE KEY `item_guid` (`itemguid`)
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--

View file

@ -0,0 +1,10 @@
ALTER TABLE character_db_version CHANGE COLUMN required_11620_01_characters_character_equipmentsets required_11704_01_characters_auction bit;
ALTER TABLE `auction`
DROP KEY `item_guid`,
ADD COLUMN `item_count` int(11) unsigned NOT NULL default '0' AFTER `item_template`,
ADD COLUMN `item_randompropertyid` int(11) NOT NULL default '0' AFTER `item_count`;
UPDATE auction, item_instance
SET auction.item_count = SUBSTRING_INDEX(SUBSTRING_INDEX(item_instance.data, ' ', 14 + 1), ' ', -1)
WHERE auction.itemguid = item_instance.guid;

View file

@ -107,11 +107,7 @@ void WorldSession::SendAuctionBidderNotification(AuctionEntry* auction)
data << uint32(auction->moneyDeliveryTime ? 0 : auction->bid);
data << uint32(auction->GetAuctionOutBid()); // AuctionOutBid?
data << uint32(auction->itemTemplate);
Item *item = sAuctionMgr.GetAItem(auction->itemGuidLow);
uint32 randomId = item ? item->GetItemRandomPropertyId() : 0;
data << uint32(randomId); // random property (value > 0) or suffix (value < 0)
data << int32(auction->itemRandomPropertyId);
SendPacket(&data);
}
@ -131,11 +127,7 @@ void WorldSession::SendAuctionOwnerNotification(AuctionEntry* auction)
// if guid!=0, client updates auctions with new bid, outbid and bidderGuid, else it shows error messages as described above
data << guid; // bidder guid
data << uint32(auction->itemTemplate); // item entry
Item *item = sAuctionMgr.GetAItem(auction->itemGuidLow);
uint32 randomId = item ? item->GetItemRandomPropertyId() : 0;
data << uint32(randomId); // random property (value > 0) or suffix (value < 0)
data << uint32(auction->itemRandomPropertyId);
float timeLeft = float(auction->moneyDeliveryTime - time(NULL)) / float(DAY);
@ -150,11 +142,7 @@ void WorldSession::SendAuctionRemovedNotification(AuctionEntry* auction)
WorldPacket data(SMSG_AUCTION_REMOVED_NOTIFICATION, (3*4));
data << uint32(auction->Id);
data << uint32(auction->itemTemplate);
Item *item = sAuctionMgr.GetAItem(auction->itemGuidLow);
uint32 randomId = item ? item->GetItemRandomPropertyId() : 0;
data << uint32(randomId); // random property (value > 0) or suffix (value < 0)
data << uint32(auction->itemRandomPropertyId);
SendPacket(&data);
}
@ -471,6 +459,8 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket & recv_data)
auction->bidder = pl->GetGUIDLow();
auction->bid = price;
SendAuctionCommandResult(auction, AUCTION_BID_PLACED, AUCTION_OK);
if (auction_owner)
auction_owner->GetSession()->SendAuctionOwnerNotification(auction);
@ -478,8 +468,6 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket & recv_data)
// after this update we should save player's money ...
CharacterDatabase.PExecute("UPDATE auction SET buyguid = '%u', lastbid = '%u' WHERE id = '%u'", auction->bidder, auction->bid, auction->Id);
SendAuctionCommandResult(auction, AUCTION_BID_PLACED, AUCTION_OK);
}
else // buyout
{
@ -497,15 +485,11 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket & recv_data)
auction->bidder = pl->GetGUIDLow();
auction->bid = auction->buyout;
GetPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_BID, auction->buyout);
auction->moneyDeliveryTime = time(NULL) + HOUR;
sAuctionMgr.SendAuctionWonMail(auction);
SendAuctionCommandResult(auction, AUCTION_BID_PLACED, AUCTION_OK);
CharacterDatabase.PExecute("UPDATE auction SET moneyTime = '" UI64FMTD "', buyguid = '%u', lastbid = '%u' WHERE id = '%u'", (uint64)auction->moneyDeliveryTime, auction->bidder, auction->bid, auction->Id);
auction->AuctionBidWinning();
GetPlayer()->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_BID, auction->buyout);
}
CharacterDatabase.BeginTransaction();
pl->SaveInventoryAndGoldToDB();

View file

@ -124,8 +124,8 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction)
uint32 owner_accid = ownerGuid ? sObjectMgr.GetPlayerAccountIdByGUID(ownerGuid) : 0;
sLog.outCommand(bidder_accId,"GM %s (Account: %u) won item in auction: %s (Entry: %u Count: %u) and pay money: %u. Original owner %s (Account: %u)",
bidder_name.c_str(), bidder_accId, pItem->GetProto()->Name1, pItem->GetEntry(), pItem->GetCount(), auction->bid, owner_name.c_str(), owner_accid);
sLog.outCommand(bidder_accId,"GM %s (Account: %u) won item in auction (Entry: %u Count: %u) and pay money: %u. Original owner %s (Account: %u)",
bidder_name.c_str(), bidder_accId, auction->itemTemplate, auction->itemCount, auction->bid, owner_name.c_str(), owner_accid);
}
}
else if (!bidder)
@ -148,8 +148,7 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction)
// set owner to bidder (to prevent delete item with sender char deleting)
// owner in `data` will set at mail receive and item extracting
CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'",auction->bidder,pItem->GetGUIDLow());
CharacterDatabase.CommitTransaction();
CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", auction->bidder, auction->itemGuidLow);
if (bidder)
{
@ -157,8 +156,10 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction)
// FIXME: for offline player need also
bidder->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1);
}
else
RemoveAItem(pItem->GetGUIDLow()); // we have to remove the item, before we delete it !!
RemoveAItem(auction->itemGuidLow); // we have to remove the item, before we delete it !!
auction->itemGuidLow = 0; // pending list will not use guid data
// will delete item or place to receiver mail list
MailDraft(msgAuctionWonSubject.str(), msgAuctionWonBody.str())
@ -168,8 +169,9 @@ void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry *auction)
// receiver not exist
else
{
CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", pItem->GetGUIDLow());
RemoveAItem(pItem->GetGUIDLow()); // we have to remove the item, before we delete it !!
CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", auction->itemGuidLow);
RemoveAItem(auction->itemGuidLow); // we have to remove the item, before we delete it !!
auction->itemGuidLow = 0;
delete pItem;
}
}
@ -269,8 +271,9 @@ void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry * auction)
if (owner)
owner->GetSession()->SendAuctionOwnerNotification(auction);
else
RemoveAItem(pItem->GetGUIDLow()); // we have to remove the item, before we delete it !!
RemoveAItem(auction->itemGuidLow); // we have to remove the item, before we delete it !!
auction->itemGuidLow = 0;
// will delete item or place to receiver mail list
MailDraft(subject.str(), "") // TODO: fix body
@ -280,8 +283,9 @@ void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry * auction)
// owner not found
else
{
CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'",pItem->GetGUIDLow());
RemoveAItem(pItem->GetGUIDLow()); // we have to remove the item, before we delete it !!
CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", auction->itemGuidLow);
RemoveAItem(auction->itemGuidLow); // we have to remove the item, before we delete it !!
auction->itemGuidLow = 0;
delete pItem;
}
}
@ -364,7 +368,7 @@ void AuctionHouseMgr::LoadAuctions()
return;
}
result = CharacterDatabase.Query("SELECT id,houseid,itemguid,item_template,itemowner,buyoutprice,time,moneyTime,buyguid,lastbid,startbid,deposit FROM auction");
result = CharacterDatabase.Query("SELECT id,houseid,itemguid,item_template,item_count,item_randompropertyid,itemowner,buyoutprice,time,moneyTime,buyguid,lastbid,startbid,deposit FROM auction");
if (!result)
{
BarGoLink bar(1);
@ -390,7 +394,10 @@ void AuctionHouseMgr::LoadAuctions()
uint32 houseid = fields[1].GetUInt32();
auction->itemGuidLow = fields[2].GetUInt32();
auction->itemTemplate = fields[3].GetUInt32();
auction->owner = fields[4].GetUInt32();
auction->itemCount = fields[4].GetUInt32();
auction->itemRandomPropertyId = fields[5].GetUInt32();
auction->owner = fields[6].GetUInt32();
if (auction->owner)
{
@ -407,15 +414,19 @@ void AuctionHouseMgr::LoadAuctions()
auction->ownerName = plWName;
}
auction->buyout = fields[5].GetUInt32();
auction->expireTime = fields[6].GetUInt32();
auction->moneyDeliveryTime = fields[7].GetUInt32();
auction->bidder = fields[8].GetUInt32();
auction->bid = fields[9].GetUInt32();
auction->startbid = fields[10].GetUInt32();
auction->deposit = fields[11].GetUInt32();
auction->buyout = fields[7].GetUInt32();
auction->expireTime = fields[8].GetUInt32();
auction->moneyDeliveryTime = fields[9].GetUInt32();
auction->bidder = fields[10].GetUInt32();
auction->bid = fields[11].GetUInt32();
auction->startbid = fields[12].GetUInt32();
auction->deposit = fields[13].GetUInt32();
auction->auctionHouseEntry = NULL; // init later
if (auction->moneyDeliveryTime)
auction->itemGuidLow = 0; // must be 0 if auction delivery pending
else
{
// check if sold item exists for guid
// and item_template in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
Item* pItem = GetAItem(auction->itemGuidLow);
@ -427,6 +438,21 @@ void AuctionHouseMgr::LoadAuctions()
continue;
}
// overwrite by real item data
if ((auction->itemTemplate != pItem->GetEntry()) ||
(auction->itemCount != pItem->GetCount()) ||
(auction->itemRandomPropertyId != pItem->GetItemRandomPropertyId()))
{
auction->itemTemplate = pItem->GetEntry();
auction->itemCount = pItem->GetCount();
auction->itemRandomPropertyId = pItem->GetItemRandomPropertyId();
//No SQL injection (no strings)
CharacterDatabase.PExecute("UPDATE auction SET item_template = %u, item_count = %u, item_randompropertyid = %i WHERE itemguid = %u",
auction->itemTemplate, auction->itemCount, auction->itemRandomPropertyId, auction->itemGuidLow);
}
}
auction->auctionHouseEntry = sAuctionHouseStore.LookupEntry(houseid);
if (!houseid)
@ -438,12 +464,19 @@ void AuctionHouseMgr::LoadAuctions()
std::ostringstream msgAuctionCanceledOwner;
msgAuctionCanceledOwner << auction->itemTemplate << ":0:" << AUCTION_CANCELED << ":0:0";
if (auction->itemGuidLow)
{
Item* pItem = GetAItem(auction->itemGuidLow);
RemoveAItem(auction->itemGuidLow);
auction->itemGuidLow = 0;
// item will deleted or added to received mail list
MailDraft(msgAuctionCanceledOwner.str(), "") // TODO: fix body
.AddItem(pItem)
.SendMailTo(MailReceiver(ObjectGuid(HIGHGUID_PLAYER, auction->owner)), auction, MAIL_CHECK_MASK_COPIED);
}
RemoveAItem(auction->itemGuidLow);
auction->DeleteFromDB();
delete auction;
@ -578,7 +611,7 @@ void AuctionHouseObject::Update()
sAuctionMgr.SendAuctionSuccessfulMail(itr->second);
itr->second->DeleteFromDB();
sAuctionMgr.RemoveAItem(itr->second->itemGuidLow);
MANGOS_ASSERT(!itr->second->itemGuidLow); // already removed or send in mail at won
delete itr->second;
RemoveAuction(itr->first);
}
@ -587,22 +620,17 @@ void AuctionHouseObject::Update()
{
if (curTime > itr->second->expireTime)
{
///- Either cancel the auction if there was no bidder
if (itr->second->bidder == 0)
///- perform the transaction if there was bidder
if (itr->second->bidder)
{
sAuctionMgr.SendAuctionExpiredMail(itr->second);
}
///- Or perform the transaction
else
{
itr->second->moneyDeliveryTime = time(NULL) + HOUR;
sAuctionMgr.SendAuctionWonMail(itr->second);
itr->second->AuctionBidWinning();
continue;
}
///- In any case clear the auction
///- cancel the auction if there was no bidder
sAuctionMgr.SendAuctionExpiredMail(itr->second);
itr->second->DeleteFromDB();
sAuctionMgr.RemoveAItem(itr->second->itemGuidLow);
delete itr->second;
RemoveAuction(itr->first);
}
@ -648,25 +676,25 @@ int AuctionEntry::CompareAuctionEntry(uint32 column, const AuctionEntry *auc, Pl
{
case 0: // level = 0
{
Item *item1 = sAuctionMgr.GetAItem(itemGuidLow);
Item *item2 = sAuctionMgr.GetAItem(auc->itemGuidLow);
if (!item1 || !item2)
ItemPrototype const* itemProto1 = ObjectMgr::GetItemPrototype(itemTemplate);
ItemPrototype const* itemProto2 = ObjectMgr::GetItemPrototype(auc->itemTemplate);
if (!itemProto2 || !itemProto1)
return 0;
if (item1->GetProto()->RequiredLevel < item2->GetProto()->RequiredLevel)
if (itemProto1->RequiredLevel < itemProto2->RequiredLevel)
return -1;
else if (item1->GetProto()->RequiredLevel > item2->GetProto()->RequiredLevel)
else if (itemProto1->RequiredLevel > itemProto2->RequiredLevel)
return +1;
break;
}
case 1: // quality = 1
{
Item *item1 = sAuctionMgr.GetAItem(itemGuidLow);
Item *item2 = sAuctionMgr.GetAItem(auc->itemGuidLow);
if (!item1 || !item2)
ItemPrototype const* itemProto1 = ObjectMgr::GetItemPrototype(itemTemplate);
ItemPrototype const* itemProto2 = ObjectMgr::GetItemPrototype(auc->itemTemplate);
if (!itemProto2 || !itemProto1)
return 0;
if (item1->GetProto()->Quality < item2->GetProto()->Quality)
if (itemProto1->Quality < itemProto2->Quality)
return -1;
else if (item1->GetProto()->Quality > item2->GetProto()->Quality)
else if (itemProto1->Quality > itemProto2->Quality)
return +1;
break;
}
@ -765,13 +793,9 @@ int AuctionEntry::CompareAuctionEntry(uint32 column, const AuctionEntry *auc, Pl
break;
case 9: // quantity = 9
{
Item *item1 = sAuctionMgr.GetAItem(itemGuidLow);
Item *item2 = sAuctionMgr.GetAItem(auc->itemGuidLow);
if (!item1 || !item2)
return 0;
if (item1->GetCount() < item2->GetCount())
if (itemCount < auc->itemCount)
return -1;
else if (item1->GetCount() > item2->GetCount())
else if (itemCount > auc->itemCount)
return +1;
break;
}
@ -889,15 +913,8 @@ void AuctionHouseObject::BuildListPendingSales(WorldPacket& data, Player* player
continue;
if (Aentry && Aentry->owner == player->GetGUIDLow())
{
Item *pItem = sAuctionMgr.GetAItem(Aentry->itemGuidLow);
if (!pItem)
{
sLog.outError("Auction: item with guid %u doesn't exist!", Aentry->itemGuidLow);
continue;
}
std::ostringstream str1;
str1 << Aentry->itemTemplate << ":" << pItem->GetItemRandomPropertyId() << ":" << AUCTION_SUCCESSFUL << ":" << Aentry->Id << ":" << pItem->GetCount();
str1 << Aentry->itemTemplate << ":" << Aentry->itemRandomPropertyId << ":" << AUCTION_SUCCESSFUL << ":" << Aentry->Id << ":" << Aentry->itemCount;
std::ostringstream str2;
str2.width(16);
@ -923,6 +940,8 @@ AuctionEntry* AuctionHouseObject::AddAuction(AuctionHouseEntry const* auctionHou
AH->Id = sObjectMgr.GenerateAuctionID();
AH->itemGuidLow = newItem->GetObjectGuid().GetCounter();
AH->itemTemplate = newItem->GetEntry();
AH->itemCount = newItem->GetCount();
AH->itemRandomPropertyId = newItem->GetItemRandomPropertyId();
AH->owner = pl ? pl->GetGUIDLow() : 0;
if (pl)
@ -1009,7 +1028,15 @@ void AuctionEntry::DeleteFromDB() const
void AuctionEntry::SaveToDB() const
{
//No SQL injection (no strings)
CharacterDatabase.PExecute("INSERT INTO auction (id,houseid,itemguid,item_template,itemowner,buyoutprice,time,moneyTime,buyguid,lastbid,startbid,deposit) "
"VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '" UI64FMTD "', '" UI64FMTD "', '%u', '%u', '%u', '%u')",
Id, auctionHouseEntry->houseId, itemGuidLow, itemTemplate, owner, buyout, (uint64)expireTime, (uint64)moneyDeliveryTime, bidder, bid, startbid, deposit);
CharacterDatabase.PExecute("INSERT INTO auction (id,houseid,itemguid,item_template,item_count,item_randompropertyid,itemowner,buyoutprice,time,moneyTime,buyguid,lastbid,startbid,deposit) "
"VALUES ('%u', '%u', '%u', '%u', '%u', '%i', '%u', '%u', '" UI64FMTD "', '" UI64FMTD "', '%u', '%u', '%u', '%u')",
Id, auctionHouseEntry->houseId, itemGuidLow, itemTemplate, itemCount, itemRandomPropertyId, owner, buyout, (uint64)expireTime, (uint64)moneyDeliveryTime, bidder, bid, startbid, deposit);
}
void AuctionEntry::AuctionBidWinning()
{
moneyDeliveryTime = time(NULL) + HOUR;
CharacterDatabase.PExecute("UPDATE auction SET itemguid = 0, moneyTime = '" UI64FMTD "', buyguid = '%u', lastbid = '%u' WHERE id = '%u'", (uint64)moneyDeliveryTime, bidder, bid, Id);
sAuctionMgr.SendAuctionWonMail(this);
}

View file

@ -56,8 +56,10 @@ enum AuctionAction
struct AuctionEntry
{
uint32 Id;
uint32 itemGuidLow;
uint32 itemGuidLow; // can be 0 after send won mail with item
uint32 itemTemplate;
uint32 itemCount;
int32 itemRandomPropertyId;
uint32 owner; // player low guid, can be 0 for server generated auction
std::wstring ownerName; // cache name for sorting
uint32 startbid; // maybe useless
@ -77,6 +79,8 @@ struct AuctionEntry
bool BuildAuctionInfo(WorldPacket & data) const;
void DeleteFromDB() const;
void SaveToDB() const;
void AuctionBidWinning();
// -1,0,+1 order result
int CompareAuctionEntry(uint32 column, const AuctionEntry *auc, Player* viewPlayer) const;

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__
#define __REVISION_NR_H__
#define REVISION_NR "11703"
#define REVISION_NR "11704"
#endif // __REVISION_NR_H__

View file

@ -1,6 +1,6 @@
#ifndef __REVISION_SQL_H__
#define __REVISION_SQL_H__
#define REVISION_DB_CHARACTERS "required_11620_01_characters_character_equipmentsets"
#define REVISION_DB_CHARACTERS "required_11704_01_characters_auction"
#define REVISION_DB_MANGOS "required_11701_01_mangos_command"
#define REVISION_DB_REALMD "required_10008_01_realmd_realmd_db_version"
#endif // __REVISION_SQL_H__