From 9ffed42772c474ee7ab6111c3cbaeb266babc1b0 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 13:01:17 -0600 Subject: [PATCH] fix: limit the count of a vendor purchase and work its price out in 64 bits A purchase took any count, and the coin, alternate currency and commendation prices were worked out as 32 bit numbers that wrap for a large count. A purchase is now for 1 to 1000 items and the prices are 64 bit numbers. Co-Authored-By: Claude Fable 5.2 --- .../Vendor/AchievementVendorComponent.cpp | 13 +++++++++++-- .../dComponents/Vendor/BaseVendorComponent.cpp | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/dGame/dComponents/Vendor/AchievementVendorComponent.cpp b/dGame/dComponents/Vendor/AchievementVendorComponent.cpp index c518503f6..ea2e42612 100644 --- a/dGame/dComponents/Vendor/AchievementVendorComponent.cpp +++ b/dGame/dComponents/Vendor/AchievementVendorComponent.cpp @@ -6,6 +6,9 @@ #include "CDItemComponentTable.h" #include "eVendorTransactionResult.h" #include "CheatDetection.h" +#include "Logger.h" +#include "Game.h" +#include "VendorPricing.h" #include "UserManager.h" #include "CDMissionsTable.h" #include "Entity.h" @@ -41,6 +44,12 @@ bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) { } void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + if (!VendorPricing::IsValidBuyCount(count)) { + LOG("Player %llu tried to buy %u of item %i from vendor %i", buyer->GetObjectID(), count, lot, m_Parent->GetLOT()); + SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + // get the item Comp from the item LOT CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); @@ -71,8 +80,8 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token" } - const uint32_t altCurrencyCost = itemComp.commendationCost * count; - if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost || !inventoryComponent->RemoveItem(costLOT, altCurrencyCost, eInventoryType::ALL)) { + const uint64_t altCurrencyCost = static_cast(itemComp.commendationCost) * count; + if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost || !inventoryComponent->RemoveItem(costLOT, static_cast(altCurrencyCost), eInventoryType::ALL)) { SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); return; } diff --git a/dGame/dComponents/Vendor/BaseVendorComponent.cpp b/dGame/dComponents/Vendor/BaseVendorComponent.cpp index b03a11d3b..0b50fe081 100644 --- a/dGame/dComponents/Vendor/BaseVendorComponent.cpp +++ b/dGame/dComponents/Vendor/BaseVendorComponent.cpp @@ -162,6 +162,12 @@ void BaseVendorComponent::HandleMrReeCameras(){ void BaseVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + // A count the client did not send as a positive number arrives here as a very large one + if (!VendorPricing::IsValidBuyCount(count)) { + LOG("Player %llu tried to buy %u of item %i from vendor %i", buyer->GetObjectID(), count, lot, m_Parent->GetLOT()); + SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } if (!SellsItem(lot)) { auto* user = UserManager::Instance()->GetUser(buyer->GetSystemAddress()); @@ -183,7 +189,7 @@ void BaseVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { // Extra currency that needs to be deducted in case of crafting auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) { - if (inventoryComponent->GetLotCount(crafintCurrencyLOT) < (crafintCurrencyCount * count)) { + if (inventoryComponent->GetLotCount(crafintCurrencyLOT) < (static_cast(crafintCurrencyCount) * count)) { SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); return; } @@ -201,24 +207,24 @@ void BaseVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { } float buyScalar = GetBuyScalar(); - const auto coinCost = static_cast(std::floor((itemComp.baseValue * buyScalar) * count)); + const uint64_t coinCost = VendorPricing::GetPriceTotal(itemComp.baseValue, buyScalar, count); Character* character = buyer->GetCharacter(); - if (!character || character->GetCoins() < coinCost) { + if (!character || character->GetCoins() < 0 || static_cast(character->GetCoins()) < coinCost) { SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); return; } if (Inventory::IsValidItem(itemComp.currencyLOT)) { - const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count; + const uint64_t altCurrencyCost = VendorPricing::GetPricePerItem(itemComp.altCurrencyCost, buyScalar, count); if (inventoryComponent->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); return; } - inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL); + inventoryComponent->RemoveItem(itemComp.currencyLOT, static_cast(altCurrencyCost), eInventoryType::ALL); } - character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR, CoinSource::Object(*m_Parent)); + character->SetCoins(character->GetCoins() - static_cast(coinCost), eLootSourceType::VENDOR, CoinSource::Object(*m_Parent)); // Live sent the vendor's LOT, and the coins paid when there were any, with the bought items LootMetrics metrics{}; metrics.sourceLot = m_Parent->GetLOT();