From b80dd8007ea42934e9c69fd653b5d147562f83c9 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 11 Dec 2017 17:05:20 -0500 Subject: [PATCH] Work on OP_MoveMultipleItems Unsure if this is ready. The one case the client does send this packet seems to work, but we need to make sure it behaves correctly if someone tries to forge a packet ... Also client doing "illegal" moves that are really legal for client needs to be resolved. --- common/eq_packet_structs.h | 6 + common/patches/rof.cpp | 63 +++++++++ common/patches/rof2.cpp | 63 +++++++++ common/patches/rof2_ops.h | 2 + common/patches/rof2_structs.h | 2 +- common/patches/rof_ops.h | 2 + common/patches/rof_structs.h | 14 ++ common/patches/titanium.cpp | 16 +++ common/patches/titanium_limits.h | 2 +- zone/client.h | 14 ++ zone/client_packet.cpp | 4 +- zone/inventory.cpp | 212 +++++++++++++++++++++++++++++++ 12 files changed, 397 insertions(+), 3 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 8452813f5..9bc0b7fbd 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1548,6 +1548,12 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItem_Struct +{ +/*0000*/ int32 count; +/*0004*/ MoveItem_Struct moves[0]; +}; + // both MoveItem_Struct/DeleteItem_Struct server structures will be changing to a structure-based slot format..this will // be used for handling SoF/SoD/etc... time stamps sent using the MoveItem_Struct format. (nothing will be done with this // info at the moment..but, it is forwarded on to the server for handling/future use) diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index b655cffb0..46f59ea52 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1762,6 +1762,36 @@ namespace RoF FINISH_ENCODE(); } + ENCODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if ((*p)->size < sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on outbound %s (MultiMoveItem_Struct): Got %d, expected at least %d", + opcodes->EmuToName((*p)->GetOpcode()), (*p)->size, + sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)); + delete *p; + *p = nullptr; + return; + } + + SETUP_VAR_ENCODE(MultiMoveItem_Struct); + + int32 count = std::abs(emu->count); + ALLOC_VAR_ENCODE(structs::MultiMoveItem_Struct, sizeof(structs::MultiMoveItem_Struct) + + sizeof(structs::MultiMoveItemSub_Struct) * count); + + OUT(count); + + for (int i = 0; i < count; ++i) { + eq->moves[i].from_slot = ServerToRoFSlot(emu->moves[i].from_slot); + eq->moves[i].to_slot = ServerToRoFSlot(emu->moves[i].to_slot); + eq->moves[i].number_in_stack = emu->moves[i].number_in_stack; + } + + FINISH_ENCODE(); + } + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } ENCODE(OP_NewZone) @@ -4870,6 +4900,39 @@ namespace RoF FINISH_DIRECT_DECODE(); } + DECODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if (__packet->size != + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on incoming %s (structs::MultiMoveItem_Struct): Got %d, expected %d", + opcodes->EmuToName(__packet->GetOpcode()), __packet->size, + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)); + __packet->SetOpcode(OP_Unknown); /* invalidate the packet */ + return; + } + + // can be negative + int32 count = std::abs(*(int32 *)__packet->pBuffer); + + unsigned char *__eq_buffer = __packet->pBuffer; + __packet->size = sizeof(MultiMoveItem_Struct) + count * sizeof(MoveItem_Struct); + __packet->pBuffer = new unsigned char[__packet->size]; + MultiMoveItem_Struct *emu = (MultiMoveItem_Struct *) __packet->pBuffer; + structs::MultiMoveItem_Struct *eq = (structs::MultiMoveItem_Struct *) __eq_buffer; + + IN(count); + + for (int i = 0; i < count; ++i) { + emu->moves[i].from_slot = RoFToServerSlot(eq->moves[i].from_slot); + emu->moves[i].to_slot = RoFToServerSlot(eq->moves[i].to_slot); + emu->moves[i].number_in_stack = eq->moves[i].number_in_stack; + } + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_PetCommands) { DECODE_LENGTH_EXACT(structs::PetCommand_Struct); diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index baf6d6b09..b945a0b72 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1831,6 +1831,36 @@ namespace RoF2 FINISH_ENCODE(); } + ENCODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if ((*p)->size < sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on outbound %s (MultiMoveItem_Struct): Got %d, expected at least %d", + opcodes->EmuToName((*p)->GetOpcode()), (*p)->size, + sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)); + delete *p; + *p = nullptr; + return; + } + + SETUP_VAR_ENCODE(MultiMoveItem_Struct); + + int32 count = std::abs(emu->count); + ALLOC_VAR_ENCODE(structs::MultiMoveItem_Struct, sizeof(structs::MultiMoveItem_Struct) + + sizeof(structs::MultiMoveItemSub_Struct) * count); + + OUT(count); + + for (int i = 0; i < count; ++i) { + eq->moves[i].from_slot = ServerToRoF2Slot(emu->moves[i].from_slot); + eq->moves[i].to_slot = ServerToRoF2Slot(emu->moves[i].to_slot); + eq->moves[i].number_in_stack = emu->moves[i].number_in_stack; + } + + FINISH_ENCODE(); + } + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } ENCODE(OP_NewZone) @@ -5109,6 +5139,39 @@ namespace RoF2 FINISH_DIRECT_DECODE(); } + DECODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if (__packet->size != + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on incoming %s (structs::MultiMoveItem_Struct): Got %d, expected %d", + opcodes->EmuToName(__packet->GetOpcode()), __packet->size, + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)); + __packet->SetOpcode(OP_Unknown); /* invalidate the packet */ + return; + } + + // can be negative + int32 count = std::abs(*(int32 *)__packet->pBuffer); + + unsigned char *__eq_buffer = __packet->pBuffer; + __packet->size = sizeof(MultiMoveItem_Struct) + count * sizeof(MoveItem_Struct); + __packet->pBuffer = new unsigned char[__packet->size]; + MultiMoveItem_Struct *emu = (MultiMoveItem_Struct *) __packet->pBuffer; + structs::MultiMoveItem_Struct *eq = (structs::MultiMoveItem_Struct *) __eq_buffer; + + IN(count); + + for (int i = 0; i < count; ++i) { + emu->moves[i].from_slot = RoF2ToServerSlot(eq->moves[i].from_slot); + emu->moves[i].to_slot = RoF2ToServerSlot(eq->moves[i].to_slot); + emu->moves[i].number_in_stack = eq->moves[i].number_in_stack; + } + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_PetCommands) { DECODE_LENGTH_EXACT(structs::PetCommand_Struct); diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 2cd81aaee..c082952f6 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -95,6 +95,7 @@ E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) +E(OP_MoveMultipleItems) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) @@ -183,6 +184,7 @@ D(OP_ItemVerifyRequest) D(OP_LoadSpellSet) D(OP_LootItem) D(OP_MoveItem) +D(OP_MoveMultipleItems) D(OP_PetCommands) D(OP_RaidInvite) D(OP_ReadBook) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 5aa11977f..7264ed8b2 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -1837,7 +1837,7 @@ struct MultiMoveItemSub_Struct struct MultiMoveItem_Struct { -/*0000*/ uint32 count; +/*0000*/ int32 count; /*0004*/ MultiMoveItemSub_Struct moves[0]; }; diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index 9030a480b..3090911e8 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -80,6 +80,7 @@ E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) +E(OP_MoveMultipleItems) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) @@ -167,6 +168,7 @@ D(OP_ItemVerifyRequest) D(OP_LoadSpellSet) D(OP_LootItem) D(OP_MoveItem) +D(OP_MoveMultipleItems) D(OP_PetCommands) D(OP_RaidInvite) D(OP_ReadBook) diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 8de4fb9ab..fbb70a4e5 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -1815,6 +1815,20 @@ struct MoveItem_Struct /*0028*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0012*/ InventorySlot_Struct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ uint8 unknown[8]; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ int32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 45f267223..9246b4129 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -47,10 +47,12 @@ namespace Titanium // server to client inventory location converters static inline int16 ServerToTitaniumSlot(uint32 serverSlot); static inline int16 ServerToTitaniumCorpseSlot(uint32 serverCorpseSlot); + //static inline structs::InventorySlot_Struct ServerToNewStyleTitaniumSlot(uint32 serverSlot, ItemPacketType PacketType = ItemPacketInvalid); // client to server inventory location converters static inline uint32 TitaniumToServerSlot(int16 titaniumSlot); static inline uint32 TitaniumToServerCorpseSlot(int16 titaniumCorpseSlot); + //static inline uint32 NewStyleTitaniumToServerSlot(structs::InventorySlot_Struct titaniumSlot, ItemPacketType PacketType = ItemPacketInvalid); // server to client text link converter static inline void ServerToTitaniumTextLink(std::string& titaniumTextLink, const std::string& serverTextLink); @@ -2474,6 +2476,20 @@ namespace Titanium return titaniumCorpseSlot; } + /*static inline uint32 NewStyleTitaniumToServerSlot(structs::InventorySlot_Struct titaniumSlot, ItemPacketType PacketType) + { + uint32 ServerSlot = INVALID_INDEX; + uint32 TempSlot = 0; + + if (titaniumSlot.Type == invtype::InvTypePossessions) { // Worn/Personal Inventory and Cursor + TempSlot = titaniumSlot.Slot; + if (titaniumSlot.SubIndex >= EQEmu::inventory::containerBegin) // Bag Slots + TempSlot = (TempSlot + 3) * EQEmu::inventory::ContainerCount + titaniumSlot.SubIndex + 1; + + ServerSlot = TempSlot; + } + }*/ + static inline void ServerToTitaniumTextLink(std::string& titaniumTextLink, const std::string& serverTextLink) { if ((constants::SayLinkBodySize == EQEmu::legacy::TEXT_LINK_BODY_LENGTH) || (serverTextLink.find('\x12') == std::string::npos)) { diff --git a/common/patches/titanium_limits.h b/common/patches/titanium_limits.h index 3266ade6f..25170ed17 100644 --- a/common/patches/titanium_limits.h +++ b/common/patches/titanium_limits.h @@ -48,7 +48,7 @@ namespace Titanium InvTypeSharedBank, InvTypeTrade, InvTypeWorld, - InvTypeLimbo, + InvTypeLimbo, // this is where titanium actually ends InvTypeTribute, InvTypeGuildTribute, InvTypeMerchant, diff --git a/zone/client.h b/zone/client.h index 88622fcff..8b1db143e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -229,6 +229,13 @@ struct ClientReward uint32 amount; }; +// used for OP_MoveMultipleItems +struct InternalMultiMoveItem { + MoveItem_Struct move; + EQEmu::ItemInstance *item; + bool move_finished; +}; + class ClientFactory { public: Client *MakeClient(std::shared_ptr ieqs); @@ -864,6 +871,13 @@ public: bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); + bool MoveMultipleItems(MultiMoveItem_Struct *move_in); + bool MoveMultipleItems(std::deque &moves, bool send_packet = false); + bool MultiMovesGetItems(std::deque &moves); + bool MultiMovesFinish(std::deque &moves); + void MultiMovesFailReturnItems(std::deque &moves); + void MultiMovesFailRevertMoves(std::deque &moves); + void SendMoveMultipleItems(std::deque &moves); void PutLootInInventory(int16 slot_id, const EQEmu::ItemInstance &inst, ServerLootItem_Struct** bag_item_data = 0); bool AutoPutLootInInventory(EQEmu::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQEmu::inventory::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index d336f7f0d..d908d15fa 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -9858,7 +9858,9 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) { - Kick(); // TODO: lets not desync though + // verify size? Translators already fail if too small + // any state checking? + MoveMultipleItems((MultiMoveItem_Struct *)app->pBuffer); } void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 49e8e83b5..442659eb1 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -2088,6 +2088,218 @@ void Client::QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call) { safe_delete(qspack); } +/* The idea behind this is that we will move all the items out of their source slots + * into a temporary holding area. Then we will move them from the holding area to their + * destination slots. These steps can fail, and we will have to revert. + * + * The reason for this is because the client will say move item from slot x to slot y + * then move item from slot y to slot x. Which means we have to some sort of temporary + * area because otherwise we would just undo the move. + */ + +bool Client::MoveMultipleItems(MultiMoveItem_Struct *move_in) +{ + // We need somewhere to hold the moves and the items need some place to live temporarily + std::deque moves; + + for (int i = 0; i < move_in->count; ++i) { + InternalMultiMoveItem m; + m.move.from_slot = move_in->moves[i].from_slot; + m.move.to_slot = move_in->moves[i].to_slot; + m.move.number_in_stack = move_in->moves[i].number_in_stack; + m.item = nullptr; + m.move_finished = false; + + if (!IsValidSlot(m.move.from_slot)) { + Log(Logs::Detail, Logs::Inventory, "Invalid slot move from slot %u to slot %u with %u charges!", m.move.from_slot, m.move.to_slot, m.move.number_in_stack); + return false; + } + + if (!IsValidSlot(m.move.to_slot)) { + Log(Logs::Detail, Logs::Inventory, "Invalid slot move from slot %u to slot %u with %u charges!", m.move.from_slot, m.move.to_slot, m.move.number_in_stack); + return false; + } + + moves.push_back(m); + } + + return MoveMultipleItems(moves); +} + +bool Client::MoveMultipleItems(std::deque &moves, bool send_packet) +{ + if (!MultiMovesGetItems(moves)) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems failed!"); + MultiMovesFailReturnItems(moves); + return false; + } + + // The client also calls a function called UpdateToSlots here, I think this will make sure items can fit in destination + // and find where to put items that need a home (ex. when bandolier needs to remove an item) + + if (!MultiMovesFinish(moves)) { + Log(Logs::Detail, Logs::Inventory, "MutliMovesFinish failed!"); + MultiMovesFailRevertMoves(moves); + MultiMovesFailReturnItems(moves); + return false; + } + + if (send_packet) + SendMoveMultipleItems(moves); + + // Now we need to save, we couldn't do it in processing because of failure + // could be better ... + // This may save slots multiple times, can rethink later ... + for (auto &m : moves) { + database.SaveInventory(character_id, m_inv.GetItem(m.move.from_slot), m.move.from_slot); + database.SaveInventory(character_id, m_inv.GetItem(m.move.to_slot), m.move.to_slot); + } + + return true; +} + +bool Client::MultiMovesFinish(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.item) { // this shouldn't happen + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish encountered a null item!"); + return false; + } + + // check lore, we could be moving from shared to non-shared bank + if (CheckLoreConflict(m.item->GetItem())) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish encountered a lore conflict!"); + return false; + } + + // check we are allowed to equip it + + auto dst_inst = m_inv.GetItem(m.move.to_slot); + // so we need to verify each of these moves are good + auto parent_slot = m_inv.CalcSlotId(m.move.to_slot); + if (parent_slot != INVALID_INDEX) { // okay, we're moving into a bag, lets verify that + auto bag_inst = m_inv.GetItem(parent_slot); + + if (!bag_inst) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a null bag!"); + return false; + } + + if (!bag_inst->IsClassBag()) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a non-bag!"); + return false; + } + + if (!m_inv.CanItemFitInContainer(m.item->GetItem(), bag_inst->GetItem())) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a bag it can't fit in!"); + return false; + } + } + // so at this point, we know we're moving into a valid bag or not moving into a bag at all, time to check others + if (dst_inst) { // okay, there is still an item here, we must be stacking + if (m.item->GetCharges() + dst_inst->GetCharges() > m.item->GetItem()->StackSize) { + Log(Logs::Detail, Logs::Inventory, "MultiMoveFinish tried to over flow a stack!"); + return false; + } + + if (m.item->GetItem()->ID != dst_inst->GetItem()->ID) { + Log(Logs::Detail, Logs::Inventory, "MutliMoveFinish tried to stack different items!"); + return false; + } + + dst_inst->SetCharges(m.item->GetCharges() + dst_inst->GetCharges()); + } else { // okay, now we're not stacking so we just need to move + m_inv.PutItem(m.move.to_slot, *m.item); + } + m.move_finished = true; + safe_delete(m.item); + } + return true; +} + +/* This function will move item instances out of the inventory into a temporary holding area + * If this returns false, we need to move them back into their source slots + */ +bool Client::MultiMovesGetItems(std::deque &moves) +{ + for (auto &m : moves) { + auto inst = m_inv.GetItem(m.move.from_slot); + if (!inst) { // trying to move an item that doesn't exist ... + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems tried to move from an empty slot!"); + return false; + } + + // if the item is stackable and we are not moving all the items out, we're splitting + if (m.move.number_in_stack > 0 && inst->IsStackable() && inst->GetCharges() != m.move.number_in_stack) { + if (inst->GetCharges() > m.move.number_in_stack) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems tried to move more items in a stack than we have!"); + return false; + } + // new instance + auto new_inst = database.CreateItem(inst->GetItem(), m.move.number_in_stack); + // modify old + inst->SetCharges(inst->GetCharges() - m.move.number_in_stack); + m.item = new_inst; + } else { + m.item = m_inv.PopItem(m.move.from_slot); // we need to steal the item! + } + } + + return true; +} + +/* This function will move the items back from the temporary holding area after a fail + */ +void Client::MultiMovesFailReturnItems(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.item) // if the item is null, we haven't stolen it yet, so nothing to fix + continue; + + // if we moved a partial stack, we need to put them all back + if (m.move.number_in_stack > 0 && m.item->IsStackable() && m.item->GetCharges() != m.move.number_in_stack) { + auto inst = m_inv.GetItem(m.move.from_slot); + if (!inst) { // well shit + safe_delete(m.item); + continue; + } + + inst->SetCharges(inst->GetCharges() + m.item->GetCharges()); + safe_delete(m.item); + } else { + m_inv.PutItem(m.move.from_slot, *m.item); + safe_delete(m.item); + } + } +} + +/* This function will move items back into the temporary holding area after a fail + */ +void Client::MultiMovesFailRevertMoves(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.move_finished) // if we haven't set this to true, we have not put the item in it's destination + continue; + m.item = m_inv.PopItem(m.move.to_slot); + } +} + +void Client::SendMoveMultipleItems(std::deque &moves) +{ + auto app = new EQApplicationPacket(OP_MoveMultipleItems, sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct) * moves.size()); + auto mmi = (MultiMoveItem_Struct *)app->pBuffer; + mmi->count = moves.size(); + int i = 0; + + for (auto &e : moves) { + mmi->moves[i].from_slot = e.move.from_slot; + mmi->moves[i].to_slot = e.move.to_slot; + mmi->moves[i].number_in_stack = e.move.number_in_stack; + ++i; + } + FastQueuePacket(&app); +} + void Client::DyeArmor(EQEmu::TintProfile* dye){ int16 slot=0; for (int i = EQEmu::textures::textureBegin; i <= EQEmu::textures::LastTintableTexture; i++) {