THIS IS A PROTOTYPE PATCH! DO NOT USE FOR LIVE SERVERS
Ok guys, here is where I am with the Resync method. It still needs some work and cleaning up.
-I used a hack technique to resync the bank slots. It involves sending a lore item multiple times, and then letting the client take care of deletions.
-Tit clients may crash when using this version of the patch since all 24 bank slots are pushed to the client. (It is 16 for Tit, right?)
-Shared bank slots cannot be fixed using this hack..they are the only undeleteable slots at this time.
-Inventory::SwapItem() was modified to reject illegal swaps in both directions and return a boolean value. (View hierarchy verified that Client::SwapItem()
was the only calling procedure.)
-The 'deleting item' code IS disabled..it shouldn't be needed even in the patch's current state.
-I had to move the two m_inv.SwapItem() calls above the corresponding mlog() statements to avoid false messages.
-A command was added to access this method manually in-game. Using '#resyncinv' (status: 0) will force a ResyncInventory() call regardless.
-This method is not capable of finding de-sync's when both the to_ and from_ items are going to valid slots. Only a zone event or relog will correct this.
-I had used IsSlotAllowed() checks in the process to push illegally assigned items to the cursor..but, I think they can be left out since Inventory::SwapItem()
'should' now catch these when they are actually occuring (and force a resync.)
-There is
A LOT OF SPAM when this method is called. However, the more slots that are occupied will reduce the amount spam that you receive. An unfortunate
side-effect of an un-ruly client...
-There are notes within the code as well, most already said above..but, some may provide additional details.
-I haven't played with BulkSendInventory() yet since I don't think it can be used to delete items.
I know there could be things that I missed, so please look this over and let me know if you think it's functionally sound. If so, I'll clean it up and make any
changes that are recommended/suggested and repost it.
I can provide testing methods if needed.
Thanks!
(350 lines..testing performed with SoF client.)
(I forgot to grab the updated patch before I left..I moved the 'token' back up to ResyncInv from the helper procedure so as not to create it each call. Either the
token or NULL is passed rather than a bool value. This patch is still as functional as the newer one..just a little less efficient)
[ResyncProto.patch]
Code:
Index: common/Item.cpp
===================================================================
--- common/Item.cpp (revision 2241)
+++ common/Item.cpp (working copy)
@@ -668,22 +668,21 @@
}
// Swap items in inventory
-void Inventory::SwapItem(sint16 slot_a, sint16 slot_b)
-{
- // Temp holding area for a
+bool Inventory::SwapItem(sint16 slot_a, sint16 slot_b) {
+ // I modified this procedure to fail CSD'd swaps placing items into illegal slots..this failure will force a resyncinv
+ // If both slots are occupied with IsSlotAllowed() items, this won't help..only a zone event or relog will fix that -U
+
+ // Temp holding areas for a & b
ItemInst* inst_a = GetItem(slot_a);
+ ItemInst* inst_b = GetItem(slot_b);
- if(inst_a)
- {
- if(!inst_a->IsSlotAllowed(slot_b))
- return;
- }
+ if(inst_a) { if(!inst_a->IsSlotAllowed(slot_b)) { return false; } }
+ if(inst_b) { if(!inst_b->IsSlotAllowed(slot_a)) { return false; } }
- // Copy b->a
- _PutItem(slot_a, GetItem(slot_b));
-
- // Copy a->b
- _PutItem(slot_b, inst_a);
+ _PutItem(slot_a, inst_b); // Copy b->a
+ _PutItem(slot_b, inst_a); // Copy a->b
+
+ return true;
}
// Checks that user has at least 'quantity' number of items in a given inventory slot
Index: common/Item.h
===================================================================
--- common/Item.h (revision 2241)
+++ common/Item.h (working copy)
@@ -155,7 +155,7 @@
sint16 PushCursor(const ItemInst& inst);
// Swap items in inventory
- void SwapItem(sint16 slot_a, sint16 slot_b);
+ bool SwapItem(sint16 slot_a, sint16 slot_b);
// Remove item from inventory
bool DeleteItem(sint16 slot_id, uint8 quantity=0);
Index: zone/client.h
===================================================================
--- zone/client.h (revision 2241)
+++ zone/client.h (working copy)
@@ -760,6 +760,8 @@
bool PushItemOnCursor(const ItemInst& inst, bool client_update = false);
void DeleteItemInInventory(sint16 slot_id, sint8 quantity = 0, bool client_update = false, bool update_db = true);
bool SwapItem(MoveItem_Struct* move_in);
+ void ResyncInventory(bool serv_called = true);
+ void ResyncClientDelItem(sint16 slot_id, bool std_struct = true);
void PutLootInInventory(sint16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data = 0);
bool AutoPutLootInInventory(ItemInst& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0);
void SummonItem(uint32 item_id, sint16 charges = 0, uint32 aug1=0, uint32 aug2=0, uint32 aug3=0, uint32 aug4=0, uint32 aug5=0, bool attuned=false, uint16 to_slot=SLOT_CURSOR);
Index: zone/client_packet.cpp
===================================================================
--- zone/client_packet.cpp (revision 2241)
+++ zone/client_packet.cpp (working copy)
@@ -3353,16 +3353,23 @@
}
if (mi_hack) { // a CSD can also cause this condition, but more likely the use of a cheat
- Message(13, "Hack detected: Illegal use of inventory bag slots!");
+ Message(15, "Hack detected: Illegal use of inventory bag slots!");
// TODO: Decide whether to log player as hacker - currently has no teeth...
// Kick();
// return;
} // End */
// if this swapitem call fails, then the server and client could be de-sync'd
- if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot))
- Message(0, "Client SwapItem request failure - verify inventory integrity.");
+ if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) {
+ Message(13, "Client OP_MoveItem request error!");
+ ResyncInventory();
+ }
+ // test code..deletable
+ Message(5, "OP_MoveItem: from_slot->%i", mi->from_slot);
+ Message(5, "OP_MoveItem: to_slot->%i", mi->to_slot);
+ Message(5, "OP_MoveItem: number_in_stack->%i", mi->number_in_stack);
+
return;
}
Index: zone/command.cpp
===================================================================
--- zone/command.cpp (revision 2241)
+++ zone/command.cpp (working copy)
@@ -459,7 +459,8 @@
command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) ||
command_add("printquestitems","Returns available quest items for multiquesting currently on the target npc.",200,command_printquestitems) ||
command_add("clearquestitems","Clears quest items for multiquesting currently on the target npc.",200,command_clearquestitems) ||
- command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", 250, command_zopp)
+ command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", 250, command_zopp) ||
+ command_add("resyncinv", "Client inventory resyncronation command. Use when you suspect an issue with your inventory.", 0, command_resyncinv)
)
{
command_deinit();
@@ -11705,3 +11706,11 @@
safe_delete(FakeItemInst);
}
}
+
+void command_resyncinv(Client *c, const Seperator *sep) {
+
+ if(!c) { return; }
+
+ c->Message(15, "Player initiated inventory resyncronization:");
+ c->ResyncInventory(false);
+}
\ No newline at end of file
Index: zone/command.h
===================================================================
--- zone/command.h (revision 2241)
+++ zone/command.h (working copy)
@@ -323,6 +323,7 @@
void command_printquestitems(Client *c, const Seperator *sep);
void command_clearquestitems(Client *c, const Seperator *sep);
void command_zopp(Client *c, const Seperator *sep);
+void command_resyncinv(Client *c, const Seperator *sep);
#ifdef EMBPERL
void command_embperl_plugin(Client *c, const Seperator *sep);
Index: zone/inventory.cpp
===================================================================
--- zone/inventory.cpp (revision 2241)
+++ zone/inventory.cpp (working copy)
@@ -1023,9 +1023,13 @@
if (!SwapItem(move_in)) // shouldn't fail because call wouldn't exist otherwise, but just in case...
this->Message(13, "Error: Internal SwapItem call returned a failure!");
}
+
+ /*
Message(13, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
LogFile->write(EQEMuLog::Debug, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
this->DeleteItemInInventory(dst_slot_id,0,true);
+ */
+
return false;
}
//verify shared bank transactions in the database
@@ -1210,9 +1214,9 @@
else {
// Nothing in destination slot: split stack into two
if ((sint16)move_in->number_in_stack >= src_inst->GetCharges()) {
+ // Move entire stack
+ if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
mlog(INVENTORY__SLOTS, "Move entire stack from %d to %d with stack size %d. Dest empty.", src_slot_id, dst_slot_id, move_in->number_in_stack);
- // Move entire stack
- m_inv.SwapItem(src_slot_id, dst_slot_id);
}
else {
// Split into two
@@ -1232,8 +1236,8 @@
}
SetMaterial(dst_slot_id,src_inst->GetItem()->ID);
}
+ if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
mlog(INVENTORY__SLOTS, "Moving entire item from slot %d to slot %d", src_slot_id, dst_slot_id);
- m_inv.SwapItem(src_slot_id, dst_slot_id);
}
int matslot = SlotConvert2(dst_slot_id);
@@ -1258,6 +1262,173 @@
return true;
}
+void Client::ResyncInventory(bool serv_called) {
+ // This is an attempt to alleviate the need to delete items during a CSD (Client-Server De-syncronization.) Part of this
+ // process is a hack to force the client to clear bank slots since they currently cannot be cleared using existing methods.
+ // The hack doesn't work for shared bank slots since they allow multiple lore items. (There is a lot of spam with this...)
+ // Best we can do for the moment is to match the shared to server and let the player re-zone, or move the de-sync'd item.
+
+ // consider forcing closed all transaction windows..what issues will arise (closed? open?)
+
+ int curs_cnt;
+ sint16 slot_id;
+
+ Message(0, "Attempting to re-syncronize %s's inventory...", GetName());
+
+#if (EQDEBUG>=5)
+ if (serv_called) { LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() called for %s", GetName()); }
+#endif
+
+ // Delete Current Cursor Array
+ for(curs_cnt = 0; curs_cnt <= 36; curs_cnt++) { // {0..36} is SoF client limit. Other clients need to be verfied
+ ResyncClientDelItem(SLOT_CURSOR);
+ }
+
+ // Sync Worn
+ for(slot_id = 0; slot_id <= 21; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ // Sync Personal
+ for(slot_id = 22; slot_id <= 29; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ // Sync Personal Bags
+ for(slot_id = 251; slot_id <= 330; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ // Sync Tribute (ERROR WITH SLOT RANGE) // consider deleting this range if it can be justified
+ for(slot_id = 400; slot_id <= 404; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ //else { ResyncClientDelItem(slot_id, false); } // enabling may cause issues..untested
+ }
+
+ // This hack, unfortunately, spams the client with 'Deleting Lore Item' messages... Of course, that's better than the
+ // player seeing a 'Deleting Item' message and losing that item due to a CSD. We can only override the shared bank slots
+ // with what is in the server-side profile, leaving the player with possibly de-sync'd shared slots. Zoning or logging
+ // clears any CSD's anyways, but this will minimize damage caused by them in the mean time.
+
+ // Sync Bank (ERROR WITH SLOT RANGE) // will probably have to limit this range for clients < SoF to avoid crashes
+ for(slot_id = 2000; slot_id <= 2023; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id, false); }
+ }
+
+ // Sync Bank Bags (ERROR WITH SLOT RANGE) // will probably have to limit this range for clients < SoF to avoid crashes
+ for(slot_id = 2031; slot_id <= 2270; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id, false); }
+ }
+
+ // Part of Bank Sync Hack Process (last token sent is not client deleted..putting it on cursor let's us delete it later)
+ ResyncClientDelItem(SLOT_CURSOR, false); // hack code
+
+ // Sync Shared Bank (ERROR WITH SLOT RANGE) // hack code doesn't work for these slots
+ for(slot_id = 2500; slot_id <= 2501; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ //else { ResyncClientDelItem(slot_id, false); } // disabled
+ }
+
+ // Sync Shared Bank Bags (ERROR WITH SLOT RANGE) // hack code doesn't work for these slots
+ for(slot_id = 2531; slot_id <= 2550; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ //else { ResyncClientDelItem(slot_id, false); } // disabled
+ }
+
+ // Sync Trader
+ for(slot_id = 3000; slot_id <= 3007; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ // Sync World Container
+ for(slot_id = 4000; slot_id <= 4009; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ // Sync Power Source
+ if(GetClientVersion() >= EQClientSoF) {
+ const ItemInst* slot_inst = m_inv[9999];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(9999); }
+ }
+
+ // Part of Bank Sync Hack Process (now it's time to delete the last resync_token)
+ ResyncClientDelItem(SLOT_CURSOR); // hack code
+
+ // Resend Updated Cursor Array
+ curs_cnt = 0; // I know there's a way to include curs_cnt inside of the for statement parameters..still needs external initiator though
+ for(std::list<ItemInst*>::const_iterator curs_iter = m_inv.cursor_begin(); curs_iter != m_inv.cursor_end(); curs_iter++) {
+ if (curs_cnt > 36) { break; }
+ SendItemPacket(SLOT_CURSOR, *curs_iter, ItemPacketSummonItem);
+ curs_cnt++;
+ }
+
+ // Sync Cursor Bags
+ for(slot_id = 331; slot_id <= 340; slot_id++) {
+ const ItemInst* slot_inst = m_inv[slot_id];
+ if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+ else { ResyncClientDelItem(slot_id); }
+ }
+
+ if (curs_cnt > 36) {
+ Message(13, "Warning: The server cursor array count exceeds the client cursor array limit!");
+ Message(15, "To avoid continued issues, remove all items from the cursor and zone or relog.");
+ }
+
+ CalcBonuses();
+ Save();
+
+ Message(14, "%s's inventory re-syncronization complete.", GetName());
+}
+
+void Client::ResyncClientDelItem(sint16 slot_id, bool std_struct) {
+ if(std_struct) {
+ EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct));
+ MoveItem_Struct* delete_slot = (MoveItem_Struct*)outapp->pBuffer;
+ delete_slot->from_slot = slot_id;
+ delete_slot->to_slot = 0xFFFFFFFF;
+ delete_slot->number_in_stack = 0; // changed from 0xFFFFFFFF (still works..matches client MoveItem_Struct info)
+
+ QueuePacket(outapp);
+ safe_delete(outapp);
+ }
+ else { // regular code still doesn't work... using hack code in the mean time
+ // hack code
+ const Item_Struct* resync_token = database.GetItem(1041); // GetItem(1041) = 'Worthless Coin'
+ ItemInst* token_inst = database.CreateItem(resync_token, 1);
+
+ SendItemPacket(slot_id, token_inst, ItemPacketTrade);
+
+ /* // regular code
+ EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct));
+ MoveItem_Struct* delete_slot = (MoveItem_Struct*)outapp->pBuffer;
+ delete_slot->from_slot = slot_id;
+ delete_slot->to_slot = 0xFFFFFFFF;
+ delete_slot->number_in_stack = 0xFFFFFFFF;
+
+ QueuePacket(outapp);
+ safe_delete(outapp);
+ */
+ }
+}
+
void Client::DyeArmor(DyeStruct* dye){
sint16 slot=0;
for(int i=0;i<7;i++){