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

Reply
 
Thread Tools Display Modes
  #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
  #2  
Old 10-24-2012, 09:49 PM
Caryatis
Dragon
 
Join Date: May 2009
Location: Milky Way
Posts: 541
Default

Heres a version that works for all clients...

Code:
void command_resyncinv(Client *c, const Seperator *sep) {
	
	if(!c) { return; }
	
	c->Disconnect();
}
Not sure of the point of this. In 3 years playing emu, I had less than 5 desyncs and everytime I just logged out and back in. If you get to the point that the server deletes an item, its already too late to use this.
Reply With Quote
  #3  
Old 10-26-2012, 09:36 AM
sorvani
Dragon
 
Join Date: May 2010
Posts: 966
Default

Client desync is unfortunately a lot more common for others than your experience has been.
Reply With Quote
  #4  
Old 10-26-2012, 09:19 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

My apologies for the condition of that patch. When my hard drive crashed a few weeks ago, my working copy was corrupted and I had to use an older
patch and try to match it up with my notes.

I've since fixed what I can and am back to working on the bank deletion process. (Not all slot ranges are active, but the ones that are do not give
any client-originating 'move item' or 'move item charge' failures.)

The external changes are still pretty much the same, but I've cleaned up ResyncInventory() and added two helper procedures.


Here are the three main procedures controlling the resync:

Code:
void Client::ResyncInventory(bool srvr_call) {
	// This method is used to re-sync client and server inventories when a SwapItem() request fails -U

	// Ranges not updated: Tribute, Trader, Trader Bags, World Container (Can re-assess later)

	// 1) NEED TO DETERMINE ALL CLIENT CURSOR LIMITS (SoF IS 37 {0..36})
	// 2) REALLY LEANING TOWARDS CLOSING ALL CONTAINERS - INCLUDING BAGS - CAN'T UPDATE TRADER OR WORLD PROPERLY
	// 3) ALL BANK SLOTS DO NOT ACCEPT NORMAL DELETE ITEM REQUESTS - CODE IN PLACE TO HANDLE FIX - FIND FIX!
	// 4) CURSOR IS IN-WORK - NEED TO ACCOMPLISH RESYNC WITHOUT SPAMMING CLIENT
	
	Message(0, "Attempting to syncronize %s's inventory... [Stand-by]", GetName());

#if (EQDEBUG>=5)
	if (srvr_call) { LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() server swapitem failure called for resync of %s's inventory", GetName()); }
#endif

	const Item_Struct* resync_token = database.GetItem(22292); // 1041 (lore) = 'Worthless Coin' / 22292 (!lore) - 'Copper Coin'
	ItemInst* token_inst = database.CreateItem(resync_token, 1);

	int cursor_cnt; // Delete Current Cursor Array
	/* IN-WORK */ //for(cursor_cnt = 0; cursor_cnt <= 36; cursor_cnt++) { SendItemPacket(SLOT_CURSOR, token_inst, ItemPacketSummonItem); }
	/* IN-WORK */ //for(cursor_cnt = 0; cursor_cnt <= 36; cursor_cnt++) { ResyncInvClDelItem(SLOT_CURSOR, NULL); }

	ResyncInvProcSlots(0, 21, token_inst); // Sync Worn
	ResyncInvProcSlots(22, 29, token_inst); // Sync Personal
	ResyncInvProcSlots(251, 330, token_inst, true, true, true); // Sync Personal Bags
	
	/* IN-WORK */ //ResyncInvClDelItem(SLOT_CURSOR, token_inst);	// Part of Bank Sync Hack Process

	if(GetClientVersion() >= EQClientSoF) {
		ResyncInvProcSlots(9999, 9999, token_inst); // Sync Power Source
		/* IN-WORK */ ResyncInvProcSlots(2000, 2023, token_inst, false, false, false); // Sync Bank (Deletion Issue with Slot Range)
		/* IN-WORK */ ResyncInvProcSlots(2031, 2270, token_inst, false, false, true); // Sync Bank Bags (Deletion Issue with Slot Range)
	}
	else {
		/* IN-WORK */ ResyncInvProcSlots(2000, 2015, token_inst, false, false, false); // Sync Bank (Deletion Issue with Slot Range)
		/* IN-WORK */ ResyncInvProcSlots(2031, 2190, token_inst, false, false, true); // Sync Bank Bags (Deletion Issue with Slot Range)
	}

	/* IN-WORK */ ResyncInvProcSlots(2500, 2501, token_inst, false, false, false); // Sync Shared Bank (Deletion Issue with Slot Range)
	/* IN-WORK */ ResyncInvProcSlots(2531, 2550, token_inst, false, false, true); // Sync Shared Bank Bags (Deletion Issue with Slot Range)
	
	/* IN-WORK */ //ResyncInvClDelItem(SLOT_CURSOR); // Part of Bank Sync Hack Process

	cursor_cnt = 0; // Sync Cursor Array
	/* IN-WORK */ /* for(std::list<ItemInst*>::const_iterator cursor_iter = m_inv.cursor_begin(); cursor_iter != m_inv.cursor_end() && cursor_cnt <=36; cursor_iter++) {
		SendItemPacket(SLOT_CURSOR, *cursor_iter, ItemPacketSummonItem);
		cursor_cnt++;
	} */

	ResyncInvProcSlots(331, 340, token_inst, true, true, true); // Sync Cursor Bags

	if (cursor_cnt > 36) { Message(15, "Caution: Server cursor count exceeds client limit! Remove cursor items and zone or relog."); }
	
	Message(14, "%s's inventory syncronization complete.", GetName());
}

void Client::ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, const ItemInst* token_inst, bool update_null, bool reg_delete, bool chk_parent) {

	for(sint16 slot_id = slot_begin; slot_id <= slot_end; slot_id++) {
		const ItemInst* slot_inst = m_inv[slot_id];

		if(chk_parent) {
			sint16 parent_slot = m_inv.CalcSlotId(slot_id);

			if(parent_slot != SLOT_INVALID && m_inv[parent_slot] && m_inv[parent_slot]->GetItem()->ItemClass == 1) {
				if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
				else {
					SendItemPacket(slot_id, token_inst, ItemPacketTrade);
					if(update_null) { ResyncInvClDelItem(slot_id, token_inst, reg_delete); }
				}
			}
		}
		else {
			if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
			else {
				SendItemPacket(slot_id, token_inst, ItemPacketTrade);
				if(update_null) { ResyncInvClDelItem(slot_id, token_inst, reg_delete); }
			}
		}
	}
}

void Client::ResyncInvClDelItem(sint16 slot_id, const ItemInst* token_inst, bool reg_delete) {
	
	if(reg_delete) {
		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
		DeleteItem_Struct* delete_slot	= (DeleteItem_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);
	}
	else { /* IN-WORK */
		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
		DeleteItem_Struct* delete_slot	= (DeleteItem_Struct*)outapp->pBuffer;
		delete_slot->from_slot			= slot_id;
		delete_slot->to_slot			= 0xFFFF; // Test value
		delete_slot->number_in_stack	= 0xFFFF; // Test value
			
		QueuePacket(outapp);
		safe_delete(outapp);
	}
	
	/* IF CAN FIGURE OUT OP_DeleteItem METHOD FOR ALTERNATE DELETE - REMOVE token_inst FROM ARGUMENTS */
}
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #5  
Old 10-28-2012, 03:26 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

THIS IS A PROTOTYPE PATCH. DO NOT USE FOR LIVE SERVERS!

Ok..this is an evolving piece of work and is probably in the ALPHA PATCH state..but don't expect any miracles until it's posted as a BETA PATCH.


('Why does he keep posting this crap, especially when it runs like #@!%??' - Well, if I, or my computer, were to die permanently, someone would have a
place to pick up, should they wish to. Plus, it let's me get feedback on concepts and implementations.)


I was having issues consistently clearing the cursor when it had more than one or two items. So, I wound up implementing a timer-based callback to ensure
that the client had enough time to 'pop' the next item in its cursor list. (I tried a 'for' loop, but the count value got to 100k with no effect..probably because the
thread was busy counting and couldn't process the SendItemPacket requests...)

'ResyncInvCallBackDelay' is critical to clearing the client cursor properly. This may need some tweaking on public servers.

This iteration does update bank items for occupied slots, but does not delete unoccupied ones due to reasons discussed on the CSD thread. (I'm trying to
avoid 'spamming' the client, so this behavior will remain in effect until I find another solution or give up searching.)

As it stands, it does not trigger any messages that are not intentional.

Callbacks are limited to 3 at the moment and the next concurrent one will Kick() the player. (Currently, once the process is beyond the callback phase, the
requests back up and no warning messages are given. This needs tweaking.)

Due to client update restrictions and untested behavior, these slot ranges are not currently activated: Tribute, Trader, Trader Bag Slots, and World Container.

I've modified some of my pre-existing changes, as well as other procedures that were causing issues when a swapitem occurred with a pre-existing CSD.


If someone could verify these for me:

-Titanium Bank Slots: (Bank: 2000 - 2015, Bank Bags: 2031 - 2190)

-SoF+ Bank Slots: (Bank: 2000 - 2015, Bank Bags: 2031 - 2270)


I'm not asking for anyone to critic the actual ResyncInv() procedures, but if someone could look at the way that I implemented the timer to check for issues
I'm ignorant to, I'd appreciate it. (It does work as it is coded.)


Thanks!


[ResyncInv.patch] (9 files affected, 506 lines)
Code:
Index: common/Item.cpp
===================================================================
--- common/Item.cpp	(revision 2241)
+++ common/Item.cpp	(working copy)
@@ -668,22 +668,20 @@
 }
 
 // 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)
+{ // Modified to fail all item swaps into illegal slots..failure will trigger a resyncinv -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)
@@ -144,7 +144,8 @@
 	inline iter_queue cursor_begin()	{ return m_cursor.begin(); }
 	inline iter_queue cursor_end()		{ return m_cursor.end(); }
 	inline bool CursorEmpty()		{ return (m_cursor.size() == 0); }
-	
+	inline int CursorSize()			{ return m_cursor.size(); }
+
 	// Retrieve a read-only item from inventory
 	inline const ItemInst* operator[](sint16 slot_id) const { return GetItem(slot_id); }
 	
@@ -155,7 +156,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.cpp
===================================================================
--- zone/client.cpp	(revision 2241)
+++ zone/client.cpp	(working copy)
@@ -324,6 +324,11 @@
 	}
 	MaxXTargets = 5;	
 	XTargetAutoAddHaters = true;
+
+	/* ResyncInv Stuff - Constructor */
+	ResyncInvCallBackTimer = new Timer(0);
+	ResyncInvCount = 0;
+	ResyncInvCursorCount = 0;
 }
 
 Client::~Client() {
@@ -406,6 +411,8 @@
 	safe_delete(taskstate);
 	safe_delete(KarmaUpdateTimer);
 	safe_delete(GlobalChatLimiterTimer);
+	/* ResyncInv Stuff - Deconstructor */
+	safe_delete(ResyncInvCallBackTimer);
 	safe_delete(qGlobals);
 	safe_delete(adventure_request_timer);
 	safe_delete(adventure_create_timer);
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);
+	/* ResyncInv Stuff - Delegates (Public:) */
+	void	ResyncInventory(bool server_call = true, bool timer_callback = false);
 	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);
@@ -1396,6 +1398,16 @@
 
 	struct XTarget_Struct XTargets[XTARGET_HARDCAP];
 
+	/* ResyncInv Stuff - Deletegates (Private:) */
+	Timer *ResyncInvCallBackTimer;
+	void ResyncInvCallBack();
+	void ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, const ItemInst* token_inst = NULL, bool update_null = true, bool reg_delete = true, bool chk_parent = false);
+	void ResyncInvClDelItem(sint16 slot_id, const ItemInst* token_inst = NULL, bool reg_delete = true);
+	int8 ResyncInvCount;
+	int8 ResyncInvCursorCount;
+	int32 ResyncInvCallBackDelay;
+#define CURSOR_LIMIT 37
+
 };
 
 #include "parser.h"
Index: zone/client_packet.cpp
===================================================================
--- zone/client_packet.cpp	(revision 2241)
+++ zone/client_packet.cpp	(working copy)
@@ -3306,63 +3306,59 @@
 				casting_spell_id);
 			database.SetMQDetectionFlag(AccountName(), GetName(), detect, zone->GetShortName());
 			safe_delete_array(detect);
+			// This Kick() can be deleted unless an automatic ban is in use. (Unremark the code below) -U
 			Kick();   // Kick client to prevent client and server from getting out-of-sync inventory slots
+			//Message(13, "Warning: You have attempted an item move while casting a spell - this action is not allowed!");
+			//ResyncInventory();
 			return;
 		}
 	}
 
-	// Added checks for illegal bagslot swaps..should help with certain cheats (currently checks personal and cursor bag slots.)
-	// - If a player has used an illegal inventory cheat, they can become bugged at some point (especially concerning lore items.)
-	//* Start
+	// Illegal bagslot useage checks. Currently, user only receives a message if this check is triggered. -U
 	bool mi_hack = false;
 
 	if (mi->from_slot >= 251 && mi->from_slot <= 340) {
-		if (mi->from_slot > 330)
-			mi_hack = true; // why are we moving from a cursor bagslot when you can't open it?
+		if (mi->from_slot > 330) { mi_hack = true; } // illegal move FROM cursor bagslot
 		else {			
-			sint16 from_invslot = Inventory::CalcSlotId(mi->from_slot);
-			const ItemInst *from_invslotitem = GetInv().GetItem(from_invslot); 
+			sint16 from_parent = m_inv.CalcSlotId(mi->from_slot);
 
-			if (!from_invslotitem) // trying to move from bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (from_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->from_slot) + 1) > from_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move from slots beyond parent container size
-			}
-			else // trying to move from bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[from_parent]) { mi_hack = true; } // illegal move FROM empty parent slot
+			else if (m_inv[from_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // illegal move FROM non-container parent
+			// illegal move FROM slot beyond parent container size
+			else if((m_inv.CalcBagIdx(mi->from_slot) + 1) > m_inv[from_parent]->GetItem()->BagSlots) { mi_hack = true; }
 		}
 	}
 
 	if (mi->to_slot >= 251 && mi->to_slot <= 340) {
-		if (mi->to_slot > 330)
-			mi_hack = true; // why are we moving to a cursor bagslot when you can't open it?
-		else {
-			sint16 to_invslot = Inventory::CalcSlotId(mi->to_slot);
-			const ItemInst *to_invslotitem = GetInv().GetItem(to_invslot);
+		if (mi->to_slot > 330) { mi_hack = true; } // illegal move TO cursor bagslot
+		else {			
+			sint16 to_parent = m_inv.CalcSlotId(mi->to_slot);
 
-			if (!to_invslotitem) // trying to move into bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (to_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->to_slot) + 1) > to_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move into slots beyond parent container size
-			}
-			else // trying to move into bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[to_parent]) { mi_hack = true; } // illegal move TO empty parent slot
+			else if (m_inv[to_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // illegal move TO non-container parent
+			// illegal move TO slot beyond parent container size
+			else if((m_inv.CalcBagIdx(mi->to_slot) + 1) > m_inv[to_parent]->GetItem()->BagSlots) { mi_hack = true; }
 		}
 	}
 
-	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!");
-		// TODO: Decide whether to log player as hacker - currently has no teeth...
-		// Kick();
-		// return;
-	} // End */
+	if (mi_hack) { Message(15, "Caution: Illegal use of inaccessable inventory bag slots!"); }
 
-	// 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();
+ 
+		if(mi_hack) {
+			Message(15, "Caution: If you use 3rd-party software to manipulate slots,");
+			Message(15, "this action may or may not correct problems in these slots.");
+			Message(15, "To ensure this issue is corrected, you must zone or re-log.");
+		}
+	}
 
+	// Test code - deleteable
+	//Message(5, "movein->from_slot: %i", mi->from_slot);
+	//Message(5, "movein->to_slot: %i", mi->to_slot);
+	//Message(5, "movein->number_in_stack: %i", mi->number_in_stack);
+
 	return;
 }
 
Index: zone/client_process.cpp
===================================================================
--- zone/client_process.cpp	(revision 2241)
+++ zone/client_process.cpp	(working copy)
@@ -746,6 +746,9 @@
 		Message(0,"Your enemies have forgotten you!");
 	}
 	
+	/* ResyncInv Stuff - Timer Check */
+	if(ResyncInvCallBackTimer && ResyncInvCallBackTimer->Enabled() && ResyncInvCallBackTimer->Check(false)) { ResyncInvCallBack(); }
+	
 	return ret;
 }
 
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();
@@ -11701,7 +11702,15 @@
 
 		ItemInst* FakeItemInst = database.CreateItem(FakeItem, charges);
 		c->SendItemPacket(slotid, FakeItemInst, packettype);
-		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u) with %i %s to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, abs(charges==1)?"charge":"charges", slotid);
+		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u), quantity: %i, to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, slotid);
 		safe_delete(FakeItemInst);
 	}
 }
+
+void command_resyncinv(Client *c, const Seperator *sep) {
+	
+	if(!c) { return; }
+	
+	c->Message(15, "Player initiated inventory resyncronization:");
+	c->ResyncInventory(false);
+}
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)
@@ -965,6 +965,7 @@
 				banker ? banker->GetName() : "UNKNOWN NPC", distance);
 			database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
 			safe_delete_array(hacked_string);
+			// This Kick() can be deleted unless an automatic ban is in use. A resyncinv is triggered on the 'return false;' -U
 			Kick();	// Kicking player to avoid item loss do to client and server inventories not being sync'd
 			return false;
 		}
@@ -1020,12 +1021,16 @@
 			move_in->from_slot = dst_slot_check;
 			move_in->to_slot = src_slot_check;
 			move_in->number_in_stack = dst_inst->GetCharges();
-			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!");
+			if (!SwapItem(move_in)) {
+#if (EQDEBUG>= 5)
+				LogFile->write(EQEMuLog::Debug, "Client::SwapItem() %s's recursive swapitem call resulted in an unknown failure.", GetName());
+#endif
+			}
 		}
-		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);
+		// This code is no longer needed :) -U
+		//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 +1215,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 +1237,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 +1263,174 @@
 	return true;
 }
 
+void Client::ResyncInventory(bool server_call, bool timer_callback) {
+	// This method is used to syncronize client and server inventories when a swapitem request fails -U
+
+	// Ranges not updated: Tribute, Trader, Trader Bags, World Container (Can reassess later)
+
+	// 1) NEED TO DETERMINE ALL CLIENT CURSOR LIMITS (SoF IS 37 {0..36})
+	// 2) REALLY LEANING TOWARDS CLOSING ALL CONTAINERS - INCLUDING BAGS - CAN'T UPDATE TRADER OR WORLD CONTAINERS PROPERLY
+	// ---(Had an open 4-slot bag in slot 25 -> used #zopp to send a 10-slot to replace it in the client -> both bags were open at the same time...)
+	// 3) ALL BANK SLOTS DO NOT ACCEPT NORMAL DELETE ITEM REQUESTS - CODE IN PLACE TO HANDLE FIX - FIND FIX!
+	// 4) USING CALLBACK TIMER TO ALLEVIATE WASTING CLOCK CYCLES - CLIENT DOESN'T IMMEDIATELY 'POP' FROM CURSOR LIST
+	// 5) CONSIDER 'STUNNING' PLAYER WHILE RESYNC IS PROCESSING - WILL IT HURT? WILL IT HELP?
+	// 6) WHEN USING BANK SLOT HACK CODE, ANY EMPTY SLOTS (OR BAG SLOTS IF PARENT IS CONTAINER) WILL GENERATE CLIENT MESSAGE
+	// 7) SHARED BANK SLOTS ARE NOT FIXABLE USING HACK CODE. CAN ONLY OVER-WRITE EXISTING AND CLIENT CAN STILL HAVE CSD'S HERE
+	// 8) NOT ALL CSD'S ARE FIXABLE USING THIS METHOD. ZONING OR LOGGING ARE THE ONLY WAY TO FIX ALL CSD ISSUES.
+	// 9) 'ResyncInvCallBackDelay' SET TO ~125MS SEEMS TO WORK..MAY NEED TO FIND CLIENT PING AND ADD %10 ~ %20
+	// 10) FUTURE WORK INCLUDES TRIMMING DOWN ARGUMENTS AND PROCESSES
+
+	/* DO INITIAL CALL WORK */
+	if(!timer_callback) {
+		if(!ResyncInvCount) {
+			Message(15, "Attempting to syncronize %s's inventory [STAND-BY]", GetName());
+
+#if (EQDEBUG>=5)
+		if(server_call) { LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() server swapitem failure called for resync of %s's inventory", GetName()); }
+#endif
+		}
+		// Close all containers - if possible
+
+		ResyncInvCallBackDelay = 125; /* SET CALLBACK DELAY HERE */
+		ResyncInvCount += 1;
+
+		if(ResyncInvCount > 3) {
+			ResyncInvCallBackTimer->Disable();
+			ResyncInvCount = 0;
+			ResyncInvCursorCount = 0;
+
+			Kick();
+		}
+		else if(ResyncInvCount > 1) {
+			Message(13, "Warning: Do not perform any actions until syncronization completes!");
+			ResyncInvCursorCount = CURSOR_LIMIT;
+		}
+		else {
+			if(m_inv.CursorSize() < CURSOR_LIMIT) { ResyncInvCursorCount = m_inv.CursorSize() + 1; }
+			else { ResyncInvCursorCount = CURSOR_LIMIT; }
+		}
+
+		ResyncInvCallBackTimer->Start(ResyncInvCallBackDelay);
+
+		return;
+	}
+
+	/* DO CURSOR DELETION WORK ON EACH CALLBACK */
+	const Item_Struct* resync_token1 = database.GetItem(22292); // 22292 = 'Copper Coin' (non-lore item)
+	ItemInst* token_inst1 = database.CreateItem(resync_token1, 1);
+
+	// Delete Current Cursor
+	SendItemPacket(SLOT_CURSOR, token_inst1, ItemPacketTrade);
+	ResyncInvClDelItem(SLOT_CURSOR, token_inst1, true);
+
+	if(ResyncInvCursorCount) { return; }
+
+	/* DO THE REST OF THE WORK NOW THAT CALLBACKS ARE OVER */
+	const Item_Struct* resync_token2 = database.GetItem(1041); // 1041 = 'Worthless Coin' (lore item)
+	ItemInst* token_inst2 = database.CreateItem(resync_token2, 1); // Part of Bank Sync Hack Process
+
+	ResyncInvProcSlots(0, 21, token_inst1); // Sync Worn
+	ResyncInvProcSlots(22, 29, token_inst1); // Sync Personal
+	ResyncInvProcSlots(251, 330, token_inst1, true, true, true); // Sync Personal Bags
+	/* BROKEN */ //ResyncInvProcSlots(400, 404, /* unknown_token_type */, false, /* unknown_delete_type */, false); // Sync Tribute
+
+	if(GetClientVersion() < EQClientSoF) {
+		/* IN-WORK */ ResyncInvProcSlots(2000, 2015, token_inst2, false, false, false); // Sync Bank (Deletion Issue)
+		/* IN-WORK */ ResyncInvProcSlots(2031, 2190, token_inst2, false, false, true); // Sync Bank Bags (Deletion Issue)
+	}
+	else {
+		ResyncInvProcSlots(9999, 9999, token_inst1); // Sync Power Source
+		/* IN-WORK */ ResyncInvProcSlots(2000, 2023, token_inst2, false, false, false); // Sync Bank (Deletion Issue)
+		/* IN-WORK */ ResyncInvProcSlots(2031, 2270, token_inst2, false, false, true); // Sync Bank Bags (Deletion Issue)
+	}
+
+	/* IN-WORK */ ResyncInvProcSlots(2500, 2501, token_inst2, false, false, false); // Sync Shared Bank (Deletion Issue)
+	/* IN-WORK */ ResyncInvProcSlots(2531, 2550, token_inst2, false, false, true); // Sync Shared Bank Bags (Deletion Issue)
+	
+	/* IN-WORK */ //ResyncInvClDelItem(SLOT_CURSOR, token_inst2); // Part of Bank Sync Hack Process
+
+	/* BROKEN */ //ResyncInvProcSlots(3000, 3007, /* unknown_token_type */, false, /* unknown_delete_type */, false); // Sync Trader
+	/* BROKEN */ //ResyncInvProcSlots(3100, 3179, /* unknown_token_type */, false, /* unknown_delete_type */, true); // Sync Trader Bags
+	/* BROKEN */ //ResyncInvProcSlots(4000, 4009, /* unknown_token_type */, false, /* unknown_delete_type */, false); // Sync World Container
+
+	int8 cursor_count = 1; // Sync Cursor Array
+	for(std::list<ItemInst*>::const_iterator cursor_iter = m_inv.cursor_begin(); cursor_iter != m_inv.cursor_end() && cursor_count <= CURSOR_LIMIT;
+		cursor_iter++, cursor_count++) { SendItemPacket(SLOT_CURSOR, *cursor_iter, ItemPacketSummonItem); }
+
+	ResyncInvProcSlots(331, 340, token_inst1, true, true, true); // Sync Cursor Bags
+
+	if (cursor_count > CURSOR_LIMIT) {
+		Message(15, "Caution: The server's cursor count exceeds client capacity.");
+		Message(15, "To avoid issues, remove all cursor items and zone or relog.");
+	}
+	
+	if(ResyncInvCount) { ResyncInvCount -= 1; }
+	if(ResyncInvCount) { ResyncInvCallBack(); }
+	else { Message(14, "%s's inventory syncronization complete", GetName()); }
+}
+
+void Client::ResyncInvCallBack() {
+
+	ResyncInvCallBackTimer->Disable();
+	ResyncInventory(false, true);
+
+	if(ResyncInvCursorCount) { ResyncInvCursorCount -= 1; ResyncInvCallBackTimer->Start(); }
+}
+
+void Client::ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, const ItemInst* token_inst, bool update_null, bool reg_delete, bool chk_parent) {
+
+	for(sint16 slot_id = slot_begin; slot_id <= slot_end; slot_id++) {
+		const ItemInst* slot_inst = m_inv[slot_id];
+
+		if(chk_parent) {
+			sint16 parent_slot = m_inv.CalcSlotId(slot_id);
+
+			if(parent_slot != SLOT_INVALID && m_inv[parent_slot] && m_inv[parent_slot]->GetItem()->ItemClass == 1) {
+				if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+				else {
+					SendItemPacket(slot_id, token_inst, ItemPacketTrade);
+					if(update_null) { ResyncInvClDelItem(slot_id, token_inst, reg_delete); }
+				}
+			}
+		}
+		else {
+			if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+			else {
+				SendItemPacket(slot_id, token_inst, ItemPacketTrade);
+				if(update_null) { ResyncInvClDelItem(slot_id, token_inst, reg_delete); }
+			}
+		}
+	}
+}
+
+void Client::ResyncInvClDelItem(sint16 slot_id, const ItemInst* token_inst, bool reg_delete) {
+	
+	if(!token_inst) { return; } // Part of Bank Sync Hack Process
+	else if(token_inst->GetID() == 1041) { SendItemPacket(slot_id, token_inst, ItemPacketTrade); } // Part of Bank Sync Hack Process
+	else if(reg_delete) {
+		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
+		DeleteItem_Struct* delete_slot	= (DeleteItem_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);
+	}
+	else { /* IN-WORK */
+		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
+		DeleteItem_Struct* delete_slot	= (DeleteItem_Struct*)outapp->pBuffer;
+		delete_slot->from_slot			= slot_id;
+		delete_slot->to_slot			= 0xFFFFFFFF;
+		delete_slot->number_in_stack	= 0xFFFF; // Test value
+			
+		QueuePacket(outapp);
+		safe_delete(outapp);
+	}
+	
+	/* IF 'OP_DeleteItem' METHOD FOR ALTERNATE DELETE CAN BE FIGURED OUT - REMOVE 'token_inst' FROM ARGUMENTS */
+}
+
 void Client::DyeArmor(DyeStruct* dye){
 	sint16 slot=0;
 	for(int i=0;i<7;i++){
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #6  
Old 10-28-2012, 04:31 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

Sorry..the bank slot range for SoF+ should be 2000 - 2023, not 2000 - 2015.

The patch is correct..that was a typo in the post.
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #7  
Old 11-02-2012, 06:13 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default Reference Post for ResyncInventory()

Code:
Slot Criteria for ResyncInventory(): (Translated to EQEmulator Titanium-based Definitions - Current as of post revision)
(These values were tested with a SoF client on a Windows 32-bit 'ReleaseBots' build -U)

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|SLOT GROUP:		|SLOT RANGE:		|UPDATE METHOD:	|DELETE METHOD:	|(NOTES:)														     |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|PERSONAL		|0 - 21 (& 9999)	|REG			|REG			|(U:REG & D:REG work properly, including 9999 for SoF+ clients)						     |
|CURSOR			|30			|REG			|REG			|(U:REG & D:REG work properly)										     |
|INVENTORY		|22 - 29		|REG			|REG			|(U:REG & D:REG work properly)										     |
|INVENTORY BAG		|251 - 330		|REG			|ALT			|(U:REG & D:REG work properly)										     |
|CURSOR BAG		|331 - 340		|REG			|ALT			|(U:REG & D:REG work properly)										     |
|TRIBUTE		|400 - 404		|UNK			|UNK			|(unverified if U:REG works properly, D:REG crashes client)						     |
|BANK			|2000 - 2015 (2023)	|REG			|HCK			|(U:REG works properly, D:REG crashes client, D:HCK allows deletion with spam, through 2023 for SoF+ clients)|
|BANK BAG		|2031 - 2190 (2270)	|REG			|ALT			|(U:REG works properly, D:REG crashes client, D:HCK allows deletion with spam, through 2270 for SoF+ clients)|
|SHARED BANK		|2500 - 2501		|REG			|UNK			|(U:REG works properly, D:REG crashes client, D:HCK not effective)					     |
|SHARED BANK BAG	|2531 - 2550		|REG			|ALT			|(U:REG works properly, D:REG crashes client, D:HCK not effective)					     |
|TRADER			|3000 - 3007		|UNK			|UNK			|(need testing results)											     |
|TRADER BAG		|3100 - 3179		|UNK			|UNK			|(need testing results)											     |
|WORLD CONTAINER	|4000 - 4009		|UNK			|UNK			|(need testing results)											     |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

UPDATE METHOD DEFINITIONS:
	Regular(REG):	{ const ItemInst* slot_inst = m_inv[slot_id];
			  SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }

	Alternate(ALT):	No satisfactory method has been found.

	Unknown(UNK):	Is an untested process, results cannot be verified, or client does not update screen item

DELETE METHOD DEFINITIONS:
	Regular(REG):	{ const Item_Struct* token_item = database.GetItem(22292);		// any non-lore item 'should' work..this one is tested
			  ItemInst* token_inst = database.CreateItem(token_item, 1);
					  
			  SendItemPacket(slot_id, token_inst, ItemPacketTrade);			// to eliminate empty slot spam, trade something to it first

			  EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
			  DeleteItem_Struct* delete_slot	= (DeleteItem_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); }	
	
	Alternate(ALT):	Alternate method for addressing ALL non-broken bag slots appears to be best method for updating bags..When parent slot is
			overwritten with non-container, client will automatically update contents. Then, a simple re-send of parent and occupied
			slots will correct the de-sync.
	
	Hack(HCK):	(This is a hack method to trick the client into doing what we can't make it do normally.)
					
			{ const Item_Struct* token_item = database.GetItem(1041);		// any lore item 'should' work..this one is tested
			  ItemInst* token_inst = database.CreateItem(token_item, 1);
					  
			  SendItemPacket(slot_id, token_inst, ItemPacketTrade);			// must be ItemPacketTrade
			  (send to every bank and bank bag slot that is empty)
					  
			  SendItemPacket(SLOT_CURSOR, token_inst, ItemPacketTrade);		// with the last on cursor, client deletes and spams all others
			  (now use REG method above to delete the cursor) }
					
	Unknown(UNK):	Is an untested process, results cannot be verified, or client does not update screen item.


DESCRIPTION:
	The purpose of this method is to correct as many client-server desyncronizations in-game as possible. It should eliminate the need for item
	deletions due to desync's, and Kick()'s can be disabled when a player performs an action that is known to cause this condition, unless an
	automatic banning process is in effect.

	Whenever a MoveItem request is received, it is processed by Client::SwapItem(). When this action fails, the ResyncInventory() method is
	called. It can also be called manually by the use of #resyncinv (a self-only command) if a player suspects an issue.

	Currently, this method cannot correct every case of a desyncronization. Some ranges are also not addressable due to the way the client handles
	certain slots. The Delete: HCK method above is a work-around to provide some solution, though it comes with undesired side-effects. This method
	is coded to try to minimize these side-effects. (3rd-party software users may not receive the resync's full benefits.)

	The workload is spread across multiple callbacks. This keeps the client active while the resync is updating and helps to eliminate errors due
	to client overload. It also allows subsequent triggers to activate properly by interrupting the current process instead of just buffering the
	request and performing an entire new cycle. The number of concurrent calls is limited to three at the moment. Once a fourth request is issued,
	the process is stopped and the client is kicked to resolve the issue.

	Due to reasons unknown, the bank slots cannot be directly deleted. The hack method employed does not correct shared bank slots due to the fact
	that they allow duplicate lore items. When trying to use the normal delete method on these slots, the client responds by crashing..an undesired
	effect. Changing pBuffer->number_in_stack to a 16-bit -1 (0xFFFF) alleviates the client crash, but serves no purpose as the action still does
	not process correctly. Using OP_MoveItem also crashes the client on these slots regardless of value. It's possible that the client can just no
	longer handle this request. (I've tested all 16-bit values of number_in_stack in these to_slot ranges: 0xFFF0 - 0x0004, 0x7FFC - 0x8003)

	When updating the bank slots, a lore item is sent to each empty one, and then to the cursor. This triggers an action in the client to delete all
	duplicate lore items. This will clear only the bank slots and any empty slots in the corresponding bags. The alternate method that I discovered
	and implemented recently, is actually more efficient as it corrects ALL bag contents without the additional deletion process. Only the parent
	bank or shared banks slots remain an issue. IF any of these slots are unoccupied, the client will still receive a 'deleting lore item from bank'
	message around the 85% mark. This is far less than the previous way, which would send them for not only bank and shared bank slots, but also for
	all empty bank and shared bank bag slots. These messages can be minimized/eliminated by ensuring that all bank/shared bank slots are occupied.

	Until all of the bugs can be tracked down and fixed, zoning or re-logging are the only 100% ways to assure server-client syncronization. This
	method only serves as an intermediate solution.


TODO:
	1) Find and/or verify all client limits (SoF: 1 active, 36-depth list (37 total); server cursor depths: {0..36} )
	2) Adjust 'delay_modifier' before 'ri_CallBackDelay' in incremental steps. The whole process probably needs tweaking for 'live' servers
	   (If you get 'CLIENT MOVE ITEM FAIL' errors, adjust the 'delay_modifier' up and retest. Continue to do this until client messages disappear)
	3) Consider making 'delay_modifier' into rule, but only if it needs to be tweaked frequently
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #8  
Old 11-02-2012, 06:26 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

THIS IS A BETA PATCH. CLIENT TESTING IS STILL NEEDED

The previous post is referenced in the patch to eliminate loading up the code.


For now, this is the best I can do. Players, 'use #resyncinv help' for a quick description of what to expect.


It probably still needs minor timing tweaking, but here it is:

[ResyncInv.patch] (9 files, 627 lines)
Code:
Index: common/Item.cpp
===================================================================
--- common/Item.cpp	(revision 2244)
+++ common/Item.cpp	(working copy)
@@ -668,22 +668,20 @@
 }
 
 // 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)
+{ // Modified to fail all item swaps into illegal slots..'return false' will trigger resync -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 2244)
+++ common/Item.h	(working copy)
@@ -144,7 +144,8 @@
 	inline iter_queue cursor_begin()	{ return m_cursor.begin(); }
 	inline iter_queue cursor_end()		{ return m_cursor.end(); }
 	inline bool CursorEmpty()		{ return (m_cursor.size() == 0); }
-	
+	inline int CursorSize()			{ return m_cursor.size(); }
+
 	// Retrieve a read-only item from inventory
 	inline const ItemInst* operator[](sint16 slot_id) const { return GetItem(slot_id); }
 	
@@ -154,8 +155,8 @@
 	// Add item to cursor queue
 	sint16 PushCursor(const ItemInst& inst);
 	
-	// Swap items in inventory
-	void SwapItem(sint16 slot_a, sint16 slot_b);
+	// Swap items in inventory (Changed to bool return function -U)
+	bool SwapItem(sint16 slot_a, sint16 slot_b);
 
 	// Remove item from inventory
 	bool DeleteItem(sint16 slot_id, uint8 quantity=0);
Index: zone/client.cpp
===================================================================
--- zone/client.cpp	(revision 2244)
+++ zone/client.cpp	(working copy)
@@ -324,6 +324,14 @@
 	}
 	MaxXTargets = 5;	
 	XTargetAutoAddHaters = true;
+
+	// ResyncInventory() Initialization Stuff
+	ri_CallBackTimer = new Timer(0);
+	ri_Count = 0;
+	ri_Step = 0;
+	ri_CursorCount = 0;
+	ri_CallBackDelay = 0;
+
 }
 
 Client::~Client() {
@@ -406,6 +414,7 @@
 	safe_delete(taskstate);
 	safe_delete(KarmaUpdateTimer);
 	safe_delete(GlobalChatLimiterTimer);
+	safe_delete(ri_CallBackTimer); // ResyncInventory() Stuff
 	safe_delete(qGlobals);
 	safe_delete(adventure_request_timer);
 	safe_delete(adventure_create_timer);
Index: zone/client.h
===================================================================
--- zone/client.h	(revision 2244)
+++ zone/client.h	(working copy)
@@ -761,6 +761,7 @@
 	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 server_call = true, bool timer_callback = false);
 	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);
@@ -1397,6 +1398,25 @@
 
 	struct XTarget_Struct XTargets[XTARGET_HARDCAP];
 
+	// ResyncInventory() Private Stuff
+	Timer *ri_CallBackTimer;
+	void ResyncInvCallBack();
+	void ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, int8 sync_method = 128);
+	void ResyncInvClDelItem(sint16 slot_id, bool alt_delete = false);
+	int8 ri_Count;
+	int8 ri_Step;
+	int8 ri_CursorCount;
+	int32 ri_CallBackDelay;
+	// float ri_CallBackDelayModifier = ??
+#define RI_COUNT_LIMIT	3
+#define RI_STEP_COUNT	20
+#define RI_CURSOR_LIMIT	37
+#define RI_BIT_AU		1		// Alternate Update
+#define RI_BIT_UN		2		// Update Null
+#define RI_BIT_AD		4		// Alternate Delete
+#define RI_BIT_CP		8		// Check Parent
+#define RI_BIT_BROKEN	128
+
 };
 
 #include "parser.h"
Index: zone/client_packet.cpp
===================================================================
--- zone/client_packet.cpp	(revision 2244)
+++ zone/client_packet.cpp	(working copy)
@@ -3306,62 +3306,51 @@
 				casting_spell_id);
 			database.SetMQDetectionFlag(AccountName(), GetName(), detect, zone->GetShortName());
 			safe_delete_array(detect);
+			// This Kick() could be deleted unless an automatic ban is in use. (Unremark the code below) -U
 			Kick();   // Kick client to prevent client and server from getting out-of-sync inventory slots
+			//Message(13, "Warning: You have attempted an item move while casting a spell - this action is not allowed!");
+			//ResyncInventory();
 			return;
 		}
 	}
 
-	// Added checks for illegal bagslot swaps..should help with certain cheats (currently checks personal and cursor bag slots.)
-	// - If a player has used an illegal inventory cheat, they can become bugged at some point (especially concerning lore items.)
-	//* Start
+	// Illegal bagslot useage checks. Currently, user only receives a message if this check is triggered. -U
 	bool mi_hack = false;
 
 	if (mi->from_slot >= 251 && mi->from_slot <= 340) {
-		if (mi->from_slot > 330)
-			mi_hack = true; // why are we moving from a cursor bagslot when you can't open it?
+		if (mi->from_slot > 330) { mi_hack = true; } // move << cursor bagslot
 		else {			
-			sint16 from_invslot = Inventory::CalcSlotId(mi->from_slot);
-			const ItemInst *from_invslotitem = GetInv().GetItem(from_invslot); 
+			sint16 from_parent = m_inv.CalcSlotId(mi->from_slot);
 
-			if (!from_invslotitem) // trying to move from bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (from_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->from_slot) + 1) > from_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move from slots beyond parent container size
-			}
-			else // trying to move from bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[from_parent]) { mi_hack = true; } // move << empty parent
+			else if (m_inv[from_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // move << non-container parent
+			else if((m_inv.CalcBagIdx(mi->from_slot) + 1) > m_inv[from_parent]->GetItem()->BagSlots) { mi_hack = true; }// move << beyond container size
 		}
 	}
 
 	if (mi->to_slot >= 251 && mi->to_slot <= 340) {
-		if (mi->to_slot > 330)
-			mi_hack = true; // why are we moving to a cursor bagslot when you can't open it?
-		else {
-			sint16 to_invslot = Inventory::CalcSlotId(mi->to_slot);
-			const ItemInst *to_invslotitem = GetInv().GetItem(to_invslot);
+		if (mi->to_slot > 330) { mi_hack = true; } // move >> cursor bagslot
+		else {			
+			sint16 to_parent = m_inv.CalcSlotId(mi->to_slot);
 
-			if (!to_invslotitem) // trying to move into bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (to_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->to_slot) + 1) > to_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move into slots beyond parent container size
-			}
-			else // trying to move into bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[to_parent]) { mi_hack = true; } // move >> empty parent 
+			else if (m_inv[to_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // move >> non-container parent
+			else if((m_inv.CalcBagIdx(mi->to_slot) + 1) > m_inv[to_parent]->GetItem()->BagSlots) { mi_hack = true; }// move >> beyond container size
 		}
 	}
 
-	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!");
-		// TODO: Decide whether to log player as hacker - currently has no teeth...
-		// Kick();
-		// return;
-	} // End */
+	if (mi_hack) { Message(15, "Caution: Illegal use of inaccessable bag slots!"); }
 
-	// 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();
+	
+		if(mi_hack) {
+			Message(15, "Caution: If you use 3rd-party software to manipulate slots,");
+			Message(15, "this action may or may not correct problems in these areas.");
+			Message(15, "To ensure inventory syncronization, you must zone or relog.");
+		}
+	}
 
 	return;
 }
Index: zone/client_process.cpp
===================================================================
--- zone/client_process.cpp	(revision 2244)
+++ zone/client_process.cpp	(working copy)
@@ -746,6 +746,9 @@
 		Message(0,"Your enemies have forgotten you!");
 	}
 	
+	// ResyncInventory() callback timer check
+	if(ri_CallBackTimer && ri_CallBackTimer->Enabled() && ri_CallBackTimer->Check(false)) { ResyncInvCallBack(); }
+
 	return ret;
 }
 
Index: zone/command.cpp
===================================================================
--- zone/command.cpp	(revision 2244)
+++ 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();
@@ -11701,7 +11702,24 @@
 
 		ItemInst* FakeItemInst = database.CreateItem(FakeItem, charges);
 		c->SendItemPacket(slotid, FakeItemInst, packettype);
-		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u) with %i %s to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, abs(charges==1)?"charge":"charges", slotid);
+		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u), quantity: %i, to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, slotid);
 		safe_delete(FakeItemInst);
 	}
 }
+
+void command_resyncinv(Client *c, const Seperator *sep) {
+	// This command can be set to GM status once all of the desync issues are found and corrected -U
+
+	if(!c) { return; }
+	else if(sep->argnum==1 && strcasecmp(sep->arg[1], "help") == 0) {
+		c->Message(0, "This command will attempt to resyncronize your inventory with the server.");
+		c->Message(0, "(Any empty bank slots will return 'lore deletion' messages for each slot)");
+		c->Message(0, "(A 'Worthless Coin' will be left in any empty shared bank slots until you");
+		c->Message(0, "zone or re-log. Clicking this coin will result in another resyncinv call)");
+	}
+	else if(sep->argnum > 0) { c->Message(15, "Usage: #resyncinv [help]"); }
+	else {
+		c->Message(15, "Player initiated inventory resyncronization:");
+		c->ResyncInventory(false);
+	}
+}
Index: zone/command.h
===================================================================
--- zone/command.h	(revision 2244)
+++ 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 2244)
+++ zone/inventory.cpp	(working copy)
@@ -965,6 +965,7 @@
 				banker ? banker->GetName() : "UNKNOWN NPC", distance);
 			database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
 			safe_delete_array(hacked_string);
+			// This Kick() could be deleted unless an automatic ban is in use. A resyncinv is triggered on the 'return false;' -U
 			Kick();	// Kicking player to avoid item loss do to client and server inventories not being sync'd
 			return false;
 		}
@@ -1020,12 +1021,16 @@
 			move_in->from_slot = dst_slot_check;
 			move_in->to_slot = src_slot_check;
 			move_in->number_in_stack = dst_inst->GetCharges();
-			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!");
+			if (!SwapItem(move_in)) {
+#if (EQDEBUG>= 5)
+				LogFile->write(EQEMuLog::Debug, "Client::SwapItem() %s's recursive swapitem call resulted in an unknown failure.", GetName());
+#endif
+			}
 		}
-		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);
+		// This code is no longer needed :) -U
+		//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 +1215,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 +1237,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 +1263,282 @@
 	return true;
 }
 
+/*
+	This method is used to syncronize client and server inventories when a swapitem request fails.
+	I believe it works as well as it's going to, given the restrictions imposed upon us by the client -U
+
+	See this post for more information:
+	http://www.eqemulator.org/forums/showpost.php?p=213911&postcount=7
+*/
+void Client::ResyncInventory(bool server_call, bool timer_callback) {
+	
+	float delay_modifier = 1.00; // adjust (1/4-point increments) to eliminate timing errors (consider making a rule)
+
+	if(!timer_callback) { /* DO INITIAL CALL WORK */
+		if(!ri_Count) {
+			Message(15, "Attempting to resyncronize %s's inventory - Please stand-by...", GetName());
+			Message(15, "(Do not use your inventory until resyncronization is complete.)");
+
+#if (EQDEBUG>=5)
+			if(server_call) { LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() server failure of swapitem called for resync of %s's inventory", GetName()); }
+#endif
+		}
+		// Close all containers - if possible
+
+		ri_CallBackTimer->Disable();
+		ri_Count += 1;
+
+		if(ri_Count > RI_COUNT_LIMIT) {
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Kick();
+			return;
+		}
+		else if(ri_Count > 1) {
+			Message(13, "Warning: Do not perform any actions until resyncronization completes!");
+			ri_CursorCount = RI_CURSOR_LIMIT;
+		}
+		else {
+			if(m_inv.CursorSize() < RI_CURSOR_LIMIT) { ri_CursorCount = m_inv.CursorSize() + 1; }
+			else { ri_CursorCount = RI_CURSOR_LIMIT; }
+		}
+
+		ri_Step = RI_STEP_COUNT;
+		ri_CallBackDelay = static_cast<int32>(250 * delay_modifier); // Step 1 callback delay (timing critical for proper operation)
+		
+		ri_CallBackTimer->Start(ri_CallBackDelay);
+
+		return;
+	}
+	
+	switch(ri_Step) { /* PARSE WORK TO STEPS FOR EACH CALLBACK */
+		case RI_STEP_COUNT: { // Delete Client CURSOR (Step 1)
+			ResyncInvClDelItem(SLOT_CURSOR);
+
+			return;
+		}
+		case 19: { // Sync WORN & PERSONAL (Step 2)
+			ResyncInvProcSlots(0, 21, RI_BIT_UN);
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(9999, 9999, RI_BIT_UN); }
+			ResyncInvProcSlots(22, 29, RI_BIT_UN);
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 3 callback delay
+			return;
+		}
+		case 18: { // Sync PERSONAL BAGS (Step 3) [First 40 slots]
+			ResyncInvProcSlots(251, 291, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 4 callback delay
+			return;
+		}
+		case 17: { // Sync PERSONAL BAGS (Step 4) [Second 40 slots]
+			ResyncInvProcSlots(291, 330, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 5 callback delay
+			return;
+		}
+		case 16: { // Sync TRIBUTE (Step 5) /* BROKEN */
+			ResyncInvProcSlots(400, 404, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 6 callback delay
+			return;
+		}
+		case 15: { // Sync BANK (Step 6)
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2000, 2023, (RI_BIT_UN | RI_BIT_AD)); }
+			else { ResyncInvProcSlots(2000, 2015, (RI_BIT_UN | RI_BIT_AD)); }
+
+			ri_CallBackDelay = static_cast<int32>(1000 * delay_modifier); // Step 7 callback delay
+			return;
+		}
+		case 14: { // Sync BANK BAGS (Step 7) [SoF+ & Ti First 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2031, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2031, 2070, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 8 callback delay
+			return;
+		}
+		case 13: { // Sync BANK BAGS (Step 8) [SoF+ & Ti Second 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2031, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2071, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 9 callback delay
+			return;
+		}
+		case 12: { // Sync BANK BAGS (Step 9) [SoF+ & Ti Third 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2111, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2111, 2150, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 10 callback delay
+			return;
+		}
+		case 11: { // Sync BANK BAGS (Step 10) [SoF+ & Ti Fourth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2111, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2151, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 11 callback delay
+			return;
+		}
+		case 10: { // Sync BANK BAGS (Step 11) [SoF+ Fifth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2191, 2230, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 12 callback delay
+			return;
+		}
+		case 9: { // Sync BANK BAGS (Step 12) [SoF+ Sixth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2231, 2270, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 13 callback delay
+			return;
+		}
+		case 8: { // Sync SHARED BANK & SHARED BANK BAGS (Step 13)
+			ResyncInvProcSlots(2500, 2501, (RI_BIT_UN | RI_BIT_AD));
+			ResyncInvProcSlots(2531, 2550, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP));
+			
+			ri_CallBackDelay = static_cast<int32>(1000 * delay_modifier); // Step 14 callback delay
+			return;
+		}
+		case 7: { // Sync TRADER (Step 14) /* BROKEN */
+			ResyncInvProcSlots(3000, 3007, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+			
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 15 callback delay
+			return;
+		}
+		case 6: { // Sync TRADER BAGS (Step 15) [First 40 Slots] /* BROKEN */
+			ResyncInvProcSlots(3100, 3139, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 500 working */ * delay_modifier); // Step 16 callback delay
+			return;
+		}
+		case 5: { // Sync TRADER BAGS (Step 16) [Second 40 slots] /* BROKEN */
+			ResyncInvProcSlots(3140, 3179, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 500 working */ * delay_modifier); // Step 17 callback delay
+			return;
+		}
+		case 4: { // Sync WORLD CONTAINER (Step 17) /* BROKEN */
+			ResyncInvProcSlots(4000, 4009, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+			
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 18 callback delay
+			return;
+		}
+		case 3: { // Delete Client CURSOR (Step 18)
+			ResyncInvClDelItem(SLOT_CURSOR, true); // hack for Bank Slots
+
+			ri_CallBackDelay = static_cast<int32>(1500 * delay_modifier); // Step 19 callback delay
+			return;
+		}
+		case 2: { // Sync Client CURSOR (Step 19)
+			ResyncInvClDelItem(SLOT_CURSOR); // hack for Bank Slots
+
+			ri_CursorCount = 1;
+			for(std::list<ItemInst*>::const_iterator cursor_iter = m_inv.cursor_begin(); cursor_iter != m_inv.cursor_end() && ri_CursorCount <= RI_CURSOR_LIMIT;
+				cursor_iter++, ri_CursorCount++) { SendItemPacket(SLOT_CURSOR, *cursor_iter, ItemPacketSummonItem); }
+
+			if(ri_Count > 1) { ri_CallBackDelay = static_cast<int32>(4000 * delay_modifier); } // Step 20 callback delay (full) }
+			else { ri_CallBackDelay = static_cast<int32>(2000 * delay_modifier); } // Step 20 callback delay (normal)
+			return;
+		}
+		case 1: { // Sync Cursor Bags (Step 20)
+			ResyncInvProcSlots(331, 340, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+			
+			ri_CallBackDelay = static_cast<int32>(100 * delay_modifier); // Finalization callback delay
+			return;
+		}
+		case 0: { // Finalization
+			if (ri_CursorCount > RI_CURSOR_LIMIT) {
+				Message(15, "Caution: The server's cursor count exceeds client capacity.");
+				Message(15, "Remove all cursor items and resycinv again to correct this.");
+			}
+
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Message(14, "%s's inventory resyncronization complete.", GetName());
+			Message(0, "(Close any container/trade windows that were open before using inventory) ");
+			return;
+		}
+		default: { // (somehow, 'ResyncInvStep' was set above the number of coded steps...)
+#if (EQDEBUG>=5)
+			LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() resyncinv for %s's inventory tried to process non-existent step %i", GetName(), ri_Step);
+#endif
+
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Kick();
+			return;
+		}
+	}
+}
+
+void Client::ResyncInvCallBack() {
+
+	int8 step_percent = ((static_cast<float>(RI_STEP_COUNT - ri_Step) / RI_STEP_COUNT) * 100);
+	if(step_percent) { Message(1, "Resycronization %i percent complete...", step_percent); }
+
+	ri_CallBackTimer->Disable();
+	ResyncInventory(false, true);
+	
+	if(ri_CursorCount && ri_Step == RI_STEP_COUNT) { ri_CursorCount -= 1; }
+	else if(ri_Step) { ri_Step -= 1; }
+
+	if(ri_CallBackDelay) { ri_CallBackTimer->Start(ri_CallBackDelay); }
+}
+
+void Client::ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, int8 sync_method) {
+
+	if(sync_method & RI_BIT_BROKEN) { return; }
+
+	for(sint16 slot_id = slot_begin; slot_id <= slot_end; slot_id++) {
+		const ItemInst* slot_inst = m_inv[slot_id];
+
+		if(sync_method & RI_BIT_CP) {
+			sint16 parent_slot = m_inv.CalcSlotId(slot_id);
+
+			if(parent_slot != SLOT_INVALID && m_inv[parent_slot] && m_inv[parent_slot]->GetItem()->ItemClass == 1) {
+				if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+				else { if(!(sync_method & RI_BIT_AU) && (sync_method & RI_BIT_UN)) { ResyncInvClDelItem(slot_id, (sync_method & RI_BIT_AD)); } }
+			}
+		}
+		else {
+			if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+			else { if(!(sync_method & RI_BIT_AU) && (sync_method & RI_BIT_UN)) { ResyncInvClDelItem(slot_id, (sync_method & RI_BIT_AD)); } }
+		}
+	}
+}
+
+void Client::ResyncInvClDelItem(sint16 slot_id, bool alt_delete) {
+
+	if(alt_delete) {
+		const Item_Struct* ri_TokenStruct = database.GetItem(1041); // 'Worthless Coin'
+		ItemInst* ri_TokenInst = database.CreateItem(ri_TokenStruct, 1);
+		
+		SendItemPacket(slot_id, ri_TokenInst, ItemPacketTrade);
+	}
+	else {
+		const Item_Struct* ri_TokenStruct = database.GetItem(22292); // 'Copper Coin'
+		ItemInst* ri_TokenInst = database.CreateItem(ri_TokenStruct, 1);
+
+		SendItemPacket(slot_id, ri_TokenInst, ItemPacketTrade);
+
+		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
+		DeleteItem_Struct* delete_slot	= (DeleteItem_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
  #9  
Old 11-03-2012, 07:56 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

For anyone testing/using this code, there is a slight oversight in it. Since this method doesn't actually do anything to the server memory/database, it causes
no permanent issues.

I had thought about this when I was first starting, but had forgotten to add the check for an existing 'Resync Token'..meaning, that the client wouldn't be
refreshed if the inventory actually contained a 'Worthless Coin' (ID: 1041).


I've resubmitted this, so hopefully this patch can be tested. I won't repost the new patch unless it doesn't get committed soon.


-U
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
Reply


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 07:21 AM.


 

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