From 0b048af5f183c0f15104da8682ec6082b9878da7 Mon Sep 17 00:00:00 2001 From: sruon Date: Tue, 27 Jan 2026 18:53:50 -0700 Subject: [PATCH] Standardize Bazaar/Synth packets, cleanup container Co-Authored-By: 0x05010705 <47504230+0x05010705@users.noreply.github.com> --- src/map/packets/c2s/0x01a_action.cpp | 14 ++++++++ src/map/packets/c2s/0x036_item_transfer.cpp | 22 +++++++----- src/map/packets/c2s/0x037_item_use.cpp | 39 +++++++++++++++++--- src/map/packets/c2s/0x096_combine_ask.cpp | 40 +++++++++++++++------ src/map/packets/c2s/0x106_bazaar_buy.cpp | 36 ++++++++++++------- src/map/trade_container.cpp | 8 ----- src/map/utils/dboxutils.cpp | 4 +-- 7 files changed, 118 insertions(+), 45 deletions(-) diff --git a/src/map/packets/c2s/0x01a_action.cpp b/src/map/packets/c2s/0x01a_action.cpp index 6eaccfbc298..b94f9fd66ab 100644 --- a/src/map/packets/c2s/0x01a_action.cpp +++ b/src/map/packets/c2s/0x01a_action.cpp @@ -408,6 +408,20 @@ void GP_CLI_COMMAND_ACTION::process(MapSession* PSession, CCharEntity* PChar) co return; } + const auto* PGysahl = PChar->getStorage(LOC_INVENTORY)->GetItem(slotID); + if (!PGysahl) + { + PChar->pushPacket(GYSAHL_GREENS, 0, MsgStd::YouDontHaveAny); + return; + } + + if (PGysahl->isSubType(ITEM_LOCKED) || PGysahl->getReserve() > 0) + { + ShowWarningFmt("GP_CLI_COMMAND_ACTION: {} trying to use invalid gysahl greens (locked/reserved)", PChar->getName()); + PChar->pushPacket(GYSAHL_GREENS, 0, MsgStd::YouDontHaveAny); + return; + } + // Consume Gysahl Green and push animation on dig attempt. if (luautils::OnChocoboDig(PChar)) { diff --git a/src/map/packets/c2s/0x036_item_transfer.cpp b/src/map/packets/c2s/0x036_item_transfer.cpp index b20ea1598b7..2c38051d6d5 100644 --- a/src/map/packets/c2s/0x036_item_transfer.cpp +++ b/src/map/packets/c2s/0x036_item_transfer.cpp @@ -62,7 +62,7 @@ auto GP_CLI_COMMAND_ITEM_TRANSFER::validate(MapSession* PSession, const CCharEnt { return PacketValidator() .isNotMonstrosity(PChar) - .range("ItemNum", ItemNum, 1, 9); + .range("ItemNum", this->ItemNum, 1, 9); } void GP_CLI_COMMAND_ITEM_TRANSFER::process(MapSession* PSession, CCharEntity* PChar) const @@ -74,11 +74,11 @@ void GP_CLI_COMMAND_ITEM_TRANSFER::process(MapSession* PSession, CCharEntity* PC return; } - CBaseEntity* PNpc = PChar->GetEntity(ActIndex, TYPE_NPC | TYPE_MOB); + CBaseEntity* PNpc = PChar->GetEntity(this->ActIndex, TYPE_NPC | TYPE_MOB); // NPC must match UniqueNo and be within 6.0' of the player if (!PNpc || - PNpc->id != UniqueNo || + PNpc->id != this->UniqueNo || distance(PChar->loc.p, PNpc->loc.p) > 6.0f) { return; @@ -92,22 +92,28 @@ void GP_CLI_COMMAND_ITEM_TRANSFER::process(MapSession* PSession, CCharEntity* PC PChar->TradeContainer->Clean(); - for (int32 slotId = 0; slotId < ItemNum; ++slotId) + for (int32 slotId = 0; slotId < this->ItemNum; ++slotId) { - const uint8_t invSlotId = PropertyItemIndexTbl[slotId]; - const uint32_t quantity = ItemNumTbl[slotId]; + const uint8_t invSlotId = this->PropertyItemIndexTbl[slotId]; + const uint32_t quantity = this->ItemNumTbl[slotId]; CItem* PItem = PChar->getStorage(LOC_INVENTORY)->GetItem(invSlotId); if (PItem == nullptr || PItem->getQuantity() < quantity) { - ShowError("GP_CLI_COMMAND_ITEM_TRANSFER: %s trying to trade NPC %s with invalid item! ", PChar->getName(), PNpc->getName()); + ShowErrorFmt("GP_CLI_COMMAND_ITEM_TRANSFER: {} trying to trade NPC {} with invalid item!", PChar->getName(), PNpc->getName()); return; } if (PItem->getReserve() > 0) { - ShowError("GP_CLI_COMMAND_ITEM_TRANSFER: %s trying to trade NPC %s with reserved item! ", PChar->getName(), PNpc->getName()); + ShowErrorFmt("GP_CLI_COMMAND_ITEM_TRANSFER: {} trying to trade NPC {} with reserved item!", PChar->getName(), PNpc->getName()); + return; + } + + if (PItem->isSubType(ITEM_LOCKED)) + { + ShowErrorFmt("GP_CLI_COMMAND_ITEM_TRANSFER: {} trying to trade NPC {} with locked item!", PChar->getName(), PNpc->getName()); return; } diff --git a/src/map/packets/c2s/0x037_item_use.cpp b/src/map/packets/c2s/0x037_item_use.cpp index 2b0e9c81631..668a79bcf50 100644 --- a/src/map/packets/c2s/0x037_item_use.cpp +++ b/src/map/packets/c2s/0x037_item_use.cpp @@ -49,13 +49,13 @@ auto GP_CLI_COMMAND_ITEM_USE::validate(MapSession* PSession, const CCharEntity* return PacketValidator() .isNotMonstrosity(PChar) .mustEqual(PChar->inMogHouse(), false, "Player is in moghouse") - .mustEqual(ItemNum, 0, "ItemNum not 0") - .oneOf("Category", static_cast(Category), validContainers); + .mustEqual(this->ItemNum, 0, "ItemNum not 0") + .oneOf("Category", static_cast(this->Category), validContainers); } void GP_CLI_COMMAND_ITEM_USE::process(MapSession* PSession, CCharEntity* PChar) const { - auto* PEntity = PChar->GetEntity(ActIndex); + auto* PEntity = PChar->GetEntity(this->ActIndex); if (!PEntity) { return; @@ -69,10 +69,41 @@ void GP_CLI_COMMAND_ITEM_USE::process(MapSession* PSession, CCharEntity* PChar) return; } + const auto* PItem = PChar->getStorage(this->Category)->GetItem(this->PropertyItemIndex); + if (!PItem) + { + return; + } + + // Equipment can be locked (equipped state) and still usable, but must actually be equipped + // Non-equipment items should never be locked + auto isEquipped = [&]() -> bool + { + for (uint8 slot = 0; slot < 18; ++slot) + { + if (PChar->equipLoc[slot] == this->Category && PChar->equip[slot] == this->PropertyItemIndex) + { + return true; + } + } + + return false; + }; + + const bool isEquipment = PItem->isType(ITEM_WEAPON) || PItem->isType(ITEM_EQUIPMENT); + const bool isLocked = PItem->isSubType(ITEM_LOCKED) && !(isEquipment && isEquipped()); + if (isLocked || + PItem->getReserve() > 0 || + PItem->getCharPrice() > 0) + { + ShowWarningFmt("GP_CLI_COMMAND_ITEM_USE: {} trying to use invalid item (locked/reserved/bazaared)", PChar->getName()); + return; + } + // TODO: Using a charged item on a non-eligible target (i.e. Soultrapper): Cannot use the on . if (PChar->UContainer->GetType() != UCONTAINER_USEITEM) { - PChar->PAI->UseItem(ActIndex, Category, PropertyItemIndex); + PChar->PAI->UseItem(this->ActIndex, this->Category, this->PropertyItemIndex); } else { diff --git a/src/map/packets/c2s/0x096_combine_ask.cpp b/src/map/packets/c2s/0x096_combine_ask.cpp index 0a46f53fe61..36d22e03e1c 100644 --- a/src/map/packets/c2s/0x096_combine_ask.cpp +++ b/src/map/packets/c2s/0x096_combine_ask.cpp @@ -67,8 +67,8 @@ const std::set validCrystals = { auto GP_CLI_COMMAND_COMBINE_ASK::validate(MapSession* PSession, const CCharEntity* PChar) const -> PacketValidationResult { return PacketValidator() - .oneOf("Crystal", static_cast(Crystal), validCrystals) - .range("Items", Items, 1, 8) + .oneOf("Crystal", static_cast(this->Crystal), validCrystals) + .range("Items", this->Items, 1, 8) .isNotMonstrosity(PChar) .isNotPreventedAction(PChar) .isNormalStatus(PChar) @@ -131,8 +131,10 @@ void GP_CLI_COMMAND_COMBINE_ASK::process(MapSession* PSession, CCharEntity* PCha PChar->CraftContainer->Clean(); - const auto PItem = PChar->getStorage(LOC_INVENTORY)->GetItem(CrystalIdx); - if (!PItem || Crystal != PItem->getID() || PItem->getQuantity() == 0) + const auto* PItem = PChar->getStorage(LOC_INVENTORY)->GetItem(this->CrystalIdx); + if (!PItem || + this->Crystal != PItem->getID() || + PItem->getQuantity() == 0) { // Detect invalid crystal usage // Prevent crafting exploit to crash on container size > 8 @@ -140,24 +142,40 @@ void GP_CLI_COMMAND_COMBINE_ASK::process(MapSession* PSession, CCharEntity* PCha return; } - uint16 itemId = Crystal; - uint8 invSlotId = CrystalIdx; + if (PItem->isSubType(ITEM_LOCKED) || PItem->getReserve() > 0) + { + ShowWarningFmt("GP_CLI_COMMAND_COMBINE_ASK: {} trying to use invalid crystal (locked/reserved)", PChar->getName()); + PChar->pushPacket(PChar, PChar, 0, 0, MsgBasic::CANNOT_USE_IN_AREA); + return; + } + + uint16 itemId = this->Crystal; + uint8 invSlotId = this->CrystalIdx; PChar->CraftContainer->setItem(0, itemId, invSlotId, 0); std::vector slotQty(MAX_CONTAINER_SIZE); - for (int32 slotId = 0; slotId < Items; ++slotId) + for (int32 slotId = 0; slotId < this->Items; ++slotId) { - itemId = ItemNo[slotId]; - invSlotId = TableNo[slotId]; + itemId = this->ItemNo[slotId]; + invSlotId = this->TableNo[slotId]; slotQty[invSlotId]++; const auto* PSlotItem = PChar->getStorage(LOC_INVENTORY)->GetItem(invSlotId); - if (PSlotItem && PSlotItem->getID() == itemId && slotQty[invSlotId] <= (PSlotItem->getQuantity() - PSlotItem->getReserve())) + if (!PSlotItem || PSlotItem->getID() != itemId) { - PChar->CraftContainer->setItem(slotId + 1, itemId, invSlotId, 1); + continue; } + + if (PSlotItem->isSubType(ITEM_LOCKED) || + slotQty[invSlotId] > (PSlotItem->getQuantity() - PSlotItem->getReserve())) + { + ShowWarningFmt("GP_CLI_COMMAND_COMBINE_ASK: {} trying to use invalid ingredient (locked/reserved)", PChar->getName()); + continue; + } + + PChar->CraftContainer->setItem(slotId + 1, itemId, invSlotId, 1); } synthutils::startSynth(PChar); diff --git a/src/map/packets/c2s/0x106_bazaar_buy.cpp b/src/map/packets/c2s/0x106_bazaar_buy.cpp index 3622c6879b7..2eb9d89913b 100644 --- a/src/map/packets/c2s/0x106_bazaar_buy.cpp +++ b/src/map/packets/c2s/0x106_bazaar_buy.cpp @@ -21,6 +21,8 @@ #include "0x106_bazaar_buy.h" +#include + #include "common/async.h" #include "entities/charentity.h" #include "packets/s2c/0x01d_item_same.h" @@ -37,7 +39,7 @@ auto GP_CLI_COMMAND_BAZAAR_BUY::validate(MapSession* PSession, const CCharEntity { // TODO: Short-circuit PV so we can bring all the other checks into this function return PacketValidator() - .range("BuyNum", BuyNum, 1, 99); + .range("BuyNum", this->BuyNum, 1, 99); } void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar) const @@ -61,7 +63,7 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar return; } - CItem* PBazaarItem = PBazaar->GetItem(BazaarItemIndex); + CItem* PBazaarItem = PBazaar->GetItem(this->BazaarItemIndex); if (PBazaarItem == nullptr || PBazaarItem->getReserve() > 0) { return; @@ -95,10 +97,20 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar return; } - if ((PBazaarItem->getCharPrice() != 0) && (PBazaarItem->getQuantity() >= BuyNum)) + if ((PBazaarItem->getCharPrice() != 0) && (PBazaarItem->getQuantity() >= this->BuyNum)) { - const uint32 Price = (PBazaarItem->getCharPrice() * BuyNum); - uint32 PriceWithTax = (PChar->loc.zone->GetTax() * Price) / 10000 + Price; + const uint64 basePrice = static_cast(PBazaarItem->getCharPrice()) * this->BuyNum; + const uint64 totalPrice = (PChar->loc.zone->GetTax() * basePrice) / 10000 + basePrice; + + if (totalPrice > std::numeric_limits::max()) + { + ShowWarningFmt("Bazaar Interaction [Price Overflow] - Buyer: {}, Seller: {}, Price: {}", PChar->name, PTarget->name, totalPrice); + PChar->pushPacket(PTarget, GP_BAZAAR_BUY_STATE::ERR); + return; + } + + const uint32 Price = static_cast(basePrice); + uint32 PriceWithTax = static_cast(totalPrice); // Validate this player can afford said item if (PCharGil->getQuantity() < PriceWithTax) @@ -114,7 +126,7 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar CItem* PItem = itemutils::GetItem(PBazaarItem); PItem->setCharPrice(0); - PItem->setQuantity(BuyNum); + PItem->setQuantity(this->BuyNum); PItem->setSubType(ITEM_UNLOCKED); if (charutils::AddItem(PChar, LOC_INVENTORY, PItem) == ERROR_SLOTID) @@ -126,7 +138,7 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar { Async::getInstance()->submit( [itemID = PItem->getID(), - quantity = BuyNum, + quantity = this->BuyNum, sellerID = PTarget->id, sellerName = PTarget->getName(), purchaserID = PChar->id, @@ -149,12 +161,12 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar PTarget->pushPacket(PChar, PItem); - charutils::UpdateItem(PTarget, LOC_INVENTORY, BazaarItemIndex, -static_cast(BuyNum)); + charutils::UpdateItem(PTarget, LOC_INVENTORY, this->BazaarItemIndex, -static_cast(this->BuyNum)); - PTarget->pushPacket(PBazaar->GetItem(BazaarItemIndex), LOC_INVENTORY, BazaarItemIndex); + PTarget->pushPacket(PBazaar->GetItem(this->BazaarItemIndex), LOC_INVENTORY, this->BazaarItemIndex); PTarget->pushPacket(); - DebugBazaarsFmt("Bazaar Interaction [Purchase Successful] - Buyer: {}, Seller: {}, Item: {}, Qty: {}, Cost: {}", PChar->name, PTarget->name, PItem->getName(), BuyNum, PriceWithTax); + DebugBazaarsFmt("Bazaar Interaction [Purchase Successful] - Buyer: {}, Seller: {}, Item: {}, Qty: {}, Cost: {}", PChar->name, PTarget->name, PItem->getName(), this->BuyNum, PriceWithTax); bool BazaarIsEmpty = true; @@ -180,9 +192,9 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar { if (PCustomer->id != PChar->id) { - PCustomer->pushPacket(PChar, BazaarItemIndex, BuyNum); + PCustomer->pushPacket(PChar, this->BazaarItemIndex, this->BuyNum); } - PCustomer->pushPacket(PBazaar->GetItem(BazaarItemIndex), BazaarItemIndex, PChar->loc.zone->GetTax()); + PCustomer->pushPacket(PBazaar->GetItem(this->BazaarItemIndex), this->BazaarItemIndex, PChar->loc.zone->GetTax()); if (BazaarIsEmpty) { diff --git a/src/map/trade_container.cpp b/src/map/trade_container.cpp index 0075cfcbc1e..98fde952d2d 100644 --- a/src/map/trade_container.cpp +++ b/src/map/trade_container.cpp @@ -278,14 +278,6 @@ void CTradeContainer::unreserveUnconfirmed() void CTradeContainer::Clean() { - for (auto* PItem : m_PItem) - { - if (PItem) - { - PItem->setReserve(0); - PItem->setSubType(ITEM_UNLOCKED); - } - } m_type = 0; m_craftType = 0; m_ItemsCount = 0; diff --git a/src/map/utils/dboxutils.cpp b/src/map/utils/dboxutils.cpp index 57ee50b1b53..8c22d99742a 100644 --- a/src/map/utils/dboxutils.cpp +++ b/src/map/utils/dboxutils.cpp @@ -124,9 +124,9 @@ void dboxutils::AddItemsToBeSent(CCharEntity* PChar, GP_CLI_COMMAND_PBX_BOXNO Bo return; } - if (PItem->getQuantity() < ItemStacks || PItem->getReserve() > 0) + if (PItem->getQuantity() < ItemStacks || PItem->getReserve() > 0 || PItem->isSubType(ITEM_LOCKED)) { - ShowWarningFmt("DBOX: {} attempted to send insufficient/reserved {}: {} ({})", PChar->getName(), ItemStacks, PItem->getName(), PItem->getID()); + ShowWarningFmt("DBOX: {} attempted to send insufficient/reserved/locked {}: {} ({})", PChar->getName(), ItemStacks, PItem->getName(), PItem->getID()); return; }