enforce stack limits when loading BB character data
Docker / Build (push) Has been cancelled

This commit is contained in:
Martin Michelsen
2025-08-06 21:23:30 -07:00
parent 5c84581978
commit a9dcd4b87e
6 changed files with 36 additions and 12 deletions
+7
View File
@@ -859,6 +859,13 @@ void Client::load_all_files() {
if (this->character_data) {
// Clear legacy play_time field
this->character_data->disp.name.clear_after_bytes(0x18);
// Enforce item stack limits, in case they've changed
auto s = this->require_server_state();
auto stack_limits = s->item_stack_limits(this->version());
this->character_data->inventory.enforce_stack_limits(stack_limits);
this->character_data->bank.enforce_stack_limits(stack_limits);
this->login->account->auto_reply_message = this->character_data->auto_reply.decode();
this->login->account->save();
this->last_play_time_update = phosg::now();
+8 -3
View File
@@ -254,9 +254,14 @@ size_t ItemData::max_stack_size(const StackLimits& limits) const {
return limits.get(this->data1[0], this->data1[1]);
}
void ItemData::enforce_min_stack_size(const StackLimits& limits) {
if (this->stack_size(limits) == 0) {
this->data1[5] = 1;
void ItemData::enforce_stack_size_limits(const StackLimits& limits) {
if (this->data1[0] == 0x03) {
size_t max_stack_size = this->max_stack_size(limits);
if (max_stack_size > 1) {
this->data1[5] = std::clamp<uint8_t>(this->data1[5], 1, max_stack_size);
} else {
this->data1[5] = 0;
}
}
}
+1 -1
View File
@@ -163,7 +163,7 @@ struct ItemData {
bool is_stackable(const StackLimits& limits) const;
size_t stack_size(const StackLimits& limits) const;
size_t max_stack_size(const StackLimits& limits) const;
void enforce_min_stack_size(const StackLimits& limits);
void enforce_stack_size_limits(const StackLimits& limits);
static bool is_common_consumable(uint32_t primary_identifier);
bool is_common_consumable() const;
+1 -1
View File
@@ -396,7 +396,7 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
}
}
}
ret.enforce_min_stack_size(*this->limits);
ret.enforce_stack_size_limits(*this->limits);
return ret;
}
+12
View File
@@ -287,6 +287,12 @@ struct PlayerInventoryT {
}
}
void enforce_stack_limits(std::shared_ptr<const ItemData::StackLimits> stack_limits) {
for (size_t z = 0; z < std::min<uint8_t>(this->num_items, this->items.size()); z++) {
this->items[z].data.enforce_stack_size_limits(*stack_limits);
}
}
operator PlayerInventoryT<!BE>() const {
PlayerInventoryT<!BE> ret;
ret.num_items = this->num_items;
@@ -410,6 +416,12 @@ struct PlayerBankT {
}
}
void enforce_stack_limits(std::shared_ptr<const ItemData::StackLimits> stack_limits) {
for (size_t z = 0; z < std::min<uint8_t>(this->num_items, this->items.size()); z++) {
this->items[z].data.enforce_stack_size_limits(*stack_limits);
}
}
template <size_t DestSlotCount, bool DestBE>
operator PlayerBankT<DestSlotCount, DestBE>() const {
PlayerBankT<DestSlotCount, DestBE> ret;
+7 -7
View File
@@ -4056,7 +4056,7 @@ static asio::awaitable<void> on_item_reward_request_bb(shared_ptr<Client> c, Sub
ItemData item;
item = cmd.item_data;
item.enforce_min_stack_size(limits);
item.enforce_stack_size_limits(limits);
item.id = l->generate_item_id(c->lobby_client_id);
// The logic for the item_create and item_create2 opcodes (B3 and B4)
@@ -4717,7 +4717,7 @@ static asio::awaitable<void> on_quest_exchange_item_bb(shared_ptr<Client> c, Sub
// TODO: We probably should use an allow-list here to prevent the client
// from creating arbitrary items if cheat mode is disabled.
ItemData new_item = cmd.replace_item;
new_item.enforce_min_stack_size(limits);
new_item.enforce_stack_size_limits(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
@@ -4775,7 +4775,7 @@ static asio::awaitable<void> on_photon_drop_exchange_for_item_bb(shared_ptr<Clie
// TODO: We probably should use an allow-list here to prevent the client
// from creating arbitrary items if cheat mode is disabled.
ItemData new_item = cmd.new_item;
new_item.enforce_min_stack_size(limits);
new_item.enforce_stack_size_limits(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
@@ -4875,7 +4875,7 @@ static asio::awaitable<void> on_secret_lottery_ticket_exchange_bb(shared_ptr<Cli
ItemData item = (s->secret_lottery_results.size() == 1)
? s->secret_lottery_results[0]
: s->secret_lottery_results[l->rand_crypt->next() % s->secret_lottery_results.size()];
item.enforce_min_stack_size(limits);
item.enforce_stack_size_limits(limits);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
@@ -4950,7 +4950,7 @@ static asio::awaitable<void> on_quest_F95E_result_bb(shared_ptr<Client> c, Subco
} else if (item.data1[0] == 0x00) {
item.data1[4] |= 0x80; // Unidentified
} else {
item.enforce_min_stack_size(*s->item_stack_limits(c->version()));
item.enforce_stack_size_limits(*s->item_stack_limits(c->version()));
}
item.id = l->generate_item_id(0xFF);
@@ -4996,7 +4996,7 @@ static asio::awaitable<void> on_quest_F95F_result_bb(shared_ptr<Client> c, Subco
send_command_t(c, 0x60, 0x00, cmd_6xDB);
ItemData new_item = result.second;
new_item.enforce_min_stack_size(limits);
new_item.enforce_stack_size_limits(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
@@ -5116,7 +5116,7 @@ static asio::awaitable<void> on_momoka_item_exchange_bb(shared_ptr<Client> c, Su
// TODO: We probably should use an allow-list here to prevent the client
// from creating arbitrary items if cheat mode is disabled.
ItemData new_item = cmd.replace_item;
new_item.enforce_min_stack_size(limits);
new_item.enforce_stack_size_limits(limits);
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);