deal with invalid 6x59 commands

This commit is contained in:
Martin Michelsen
2023-11-03 23:10:07 -07:00
parent f63b4bd88b
commit a7e478780e
4 changed files with 47 additions and 8 deletions
+4
View File
@@ -322,6 +322,10 @@ bool Lobby::item_exists(uint32_t item_id) const {
return this->item_id_to_floor_item.count(item_id); return this->item_id_to_floor_item.count(item_id);
} }
const Lobby::FloorItem& Lobby::find_item(uint32_t item_id) const {
return this->item_id_to_floor_item.at(item_id);
}
void Lobby::add_item(const ItemData& data, uint8_t area, float x, float z) { void Lobby::add_item(const ItemData& data, uint8_t area, float x, float z) {
auto& fi = this->item_id_to_floor_item[data.id]; auto& fi = this->item_id_to_floor_item[data.id];
fi.data = data; fi.data = data;
+1
View File
@@ -173,6 +173,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint64_t serial_number = 0); uint64_t serial_number = 0);
bool item_exists(uint32_t item_id) const; bool item_exists(uint32_t item_id) const;
const FloorItem& find_item(uint32_t item_id) const;
void add_item(const ItemData& item, uint8_t area, float x, float z); void add_item(const ItemData& item, uint8_t area, float x, float z);
ItemData remove_item(uint32_t item_id); ItemData remove_item(uint32_t item_id);
uint32_t generate_item_id(uint8_t client_id); uint32_t generate_item_id(uint8_t client_id);
+5 -4
View File
@@ -338,10 +338,11 @@ void SavedPlayerDataBB::add_item(const ItemData& item) {
// If we found an existing stack, add it to the total and return // If we found an existing stack, add it to the total and return
if (y < this->inventory.num_items) { if (y < this->inventory.num_items) {
this->inventory.items[y].data.data1[5] += item.data1[5]; size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
if (this->inventory.items[y].data.data1[5] > combine_max) { if (new_stack_size > combine_max) {
this->inventory.items[y].data.data1[5] = combine_max; throw out_of_range("stack is too large");
} }
this->inventory.items[y].data.data1[5] = new_stack_size;
return; return;
} }
} }
@@ -349,7 +350,7 @@ void SavedPlayerDataBB::add_item(const ItemData& item) {
// If we get here, then it's not meseta and not a combine item, so it needs to // If we get here, then it's not meseta and not a combine item, so it needs to
// go into an empty inventory slot // go into an empty inventory slot
if (this->inventory.num_items >= 30) { if (this->inventory.num_items >= 30) {
throw runtime_error("inventory is full"); throw out_of_range("inventory is full");
} }
auto& inv_item = this->inventory.items[this->inventory.num_items]; auto& inv_item = this->inventory.items[this->inventory.num_items];
inv_item.present = 1; inv_item.present = 1;
+37 -4
View File
@@ -973,11 +973,44 @@ static void on_pick_up_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
} }
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto effective_p = effective_c->game_data.player();
auto item = l->remove_item(cmd.item_id);
effective_p->add_item(item);
auto s = c->require_server_state(); auto s = c->require_server_state();
auto effective_p = effective_c->game_data.player();
// It seems the client just plays it fast and loose with these commands.
// There can be multiple 6x5A (request to pick up item) commands in flight,
// and the leader will respond to *all* of them with 6x59 (pick up item),
// even if the item can't be picked up by the time the command is processed
// (e.g. if the player's inventory has become full due to a previous pick up
// request). In the case of a full inventory, the client is expected to just
// ignore the command; in the case of the floor item not existing, however,
// the client behaves strangely and eventually disconnects, so we let
// l->remove_item throw in that case instead to fail faster.
// This might be a legitimate bug in the client: the logic for determining
// if an item can be picked up also applies to Meseta, and forbids the
// pickup if the client has 999999 on hand. However, some actions that
// affect Meseta aren't sent to other players (e.g. depositing it in the
// bank), so the game could get into a state where some players see a client
// as able to pick up a Meseta item and some see a client as unable to do
// so. The downstream result of this desynchronization is that if the
// affected player tries to pick up some Meseta, some clients will not allow
// the pickup and will not delete the floor item, so if someone else tries
// to pick it up again, they will disconnect.
auto item = l->remove_item(cmd.item_id);
try {
effective_p->add_item(item);
} catch (const out_of_range& e) {
auto name = s->describe_item(c->version(), item, false);
l->log.warning("Player %hu attempted to pick up %08" PRIX32 " (%s) but cannot (%s); ignoring command",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), e.what());
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5PICK/F %08" PRIX32 "\n%s\n$C4%s", cmd.item_id.load(), name.c_str(), e.what());
}
return;
}
auto name = s->describe_item(c->version(), item, false); auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu picked up %08" PRIX32 " (%s)", l->log.info("Player %hu picked up %08" PRIX32 " (%s)",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());