Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #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
 


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 03:03 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3