fix: Fixes exploits with bank box deposits. Makes stacking more efficient (#2337)

### Summary

- Fix AccountGold gold duplication exploit: When AccountGold.Enabled was true, double-clicking a BankCheck deposited
the full value to the account but then continued creating physical gold piles for the same amount, duplicating the
value
- Fix Deposit/DepositUpTo partial deposit exploit: Both methods created new max-size gold piles and checks without
first filling existing partial stacks, wasting container slots and causing premature "bank full" failures that could
be leveraged to manipulate gold distribution
- Fix BagOfSending bank stacking: Gold and BankCheck items sent via BagOfSending now use Banker.Deposit for efficient
stacking instead of naive TryDropItem, which could fail on a full bank even when existing piles had room
- Improve gold/check deposit efficiency: Banker.Deposit and Banker.DepositUpTo now top off existing gold piles (up to
60k) and bank checks (up to 1M) before creating new items, maximizing use of available container slots
This commit is contained in:
Kamron Batman 2026-02-13 20:34:35 -08:00 • committed by GitHub
parent f7eab79adb
commit 4b392079e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 232 additions and 102 deletions

View file

@ -137,68 +137,69 @@ public partial class BankCheck : Item
return;
}
var deposited = 0;
var toAdd = _worth;
if (AccountGold.Enabled && from.Account?.DepositGold(toAdd) == true)
if (AccountGold.Enabled)
{
deposited = toAdd;
}
while (toAdd > 0)
{
var amount = Math.Min(toAdd, 60000);
var gold = new Gold(amount);
if (box.TryDropItem(from, gold, false))
if (from.Account?.DepositGold(_worth) != true)
{
toAdd -= amount;
deposited += amount;
return;
}
else
{
gold.Delete();
break;
}
}
if (deposited >= _worth)
{
Delete();
// Gold was deposited in your account:
from.SendLocalizedMessage(1042672, true, $"{_worth:N0}");
}
else
{
Worth -= deposited;
}
// Internalize the check to free its slot for deposit.
// reserveSlots: 1 conditionally keeps a slot free for the check to bounce back
// only if the full amount won't fit — if it fits, no reservation is needed.
RecordBounce();
Internalize();
var deposited = Banker.DepositUpTo(from, _worth, false, 1);
if (deposited >= _worth)
{
Delete();
}
else if (deposited > 0)
{
Worth -= deposited;
Bounce(from);
}
else
{
Bounce(from);
from.SendLocalizedMessage(500390); // Your bank box is full.
return;
}
if (deposited > 0)
{
// Gold was deposited in your account:
from.SendLocalizedMessage(1042672, true, $"{deposited:N0}");
}
if (from is PlayerMobile pm)
if (from is PlayerMobile pm)
{
var qs = pm.Quest;
if (qs is DarkTidesQuest)
{
var qs = pm.Quest;
var obj = qs.FindObjective<CashBankCheckObjective>();
if (qs is DarkTidesQuest)
if (obj?.Completed == false)
{
var obj = qs.FindObjective<CashBankCheckObjective>();
if (obj?.Completed == false)
{
obj.Complete();
}
obj.Complete();
}
}
if (qs is UzeraanTurmoilQuest)
if (qs is UzeraanTurmoilQuest)
{
var obj = qs.FindObjective(typeof(Engines.Quests.Haven.CashBankCheckObjective));
if (obj?.Completed == false)
{
var obj = qs.FindObjective(typeof(Engines.Quests.Haven.CashBankCheckObjective));
if (obj?.Completed == false)
{
obj.Complete();
}
obj.Complete();
}
}
}

View file

@ -5,6 +5,7 @@ using Server.ContextMenus;
using Server.Engines.Quests;
using Server.Regions;
using Server.Spells;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Items;
@ -233,16 +234,35 @@ public partial class BagOfSending : Item, TranslocationItem
{
from.SendLocalizedMessage(1079932); // You don't have enough charges to send that much weight
}
else if (!from.BankBox.TryDropItem(from, item, false))
{
_bag.SendLocalizedMessageTo(from, 1054110, 0x59); // Your bank box is full.
}
else
{
_bag.Charges -= Core.ML ? reqCharges : 1;
bool sent;
// The item was placed in your bank box.
_bag.SendLocalizedMessageTo(from, 1054150, 0x59);
if (item is Gold or BankCheck)
{
var amount = (item as Gold)?.Amount ?? ((BankCheck)item).Worth;
sent = Banker.Deposit(from, amount);
if (sent)
{
item.Delete();
}
}
else
{
sent = from.BankBox.TryDropItem(from, item, false);
}
if (sent)
{
_bag.Charges -= Core.ML ? reqCharges : 1;
// The item was placed in your bank box.
_bag.SendLocalizedMessageTo(from, 1054150, 0x59);
}
else
{
_bag.SendLocalizedMessageTo(from, 1054110, 0x59); // Your bank box is full.
}
}
}
}