View Single Post
  #1  
Old 10-24-2012, 09:26 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default ResyncInventory()

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++){
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote