Browse Source

protection for lua user data inner data being deleted, protection around conversation options

Emagi 1 year ago
parent
commit
f57a91d0cb

+ 2 - 0
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2983,6 +2983,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			Spawn* spawn = cmdTarget;
 			if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){
 				Item* item = spawn->LootItem(atoul(sep->arg[0]));
+				lua_interface->SetLuaUserDataStale(item);
 				safe_delete(item);
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed item.");
 			}
@@ -3099,6 +3100,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					client->RemovePendingQuest(quest);
 					if(lua_interface)
 						lua_interface->CallQuestFunction(quest, "Declined", client->GetPlayer());
+					lua_interface->SetLuaUserDataStale(quest);
 					safe_delete(quest);
 					client->GetCurrentZone()->SendQuestUpdates(client);
 				}

+ 5 - 0
EQ2/source/WorldServer/Items/Items.cpp

@@ -2888,6 +2888,7 @@ bool PlayerItemList::AddItem(Item* item){ //is called with a slot already set
 				if(item->details.slot_id > bag->details.num_slots){
 					LogWrite(ITEM__ERROR, 0, "Item", "Error Adding Item: Invalid slot for item unique id: %u (%s - %i), InvSlotID: %u, slotid: %u, numslots: %u", item->details.unique_id, item->name.c_str(), 
 					item->details.item_id, item->details.inv_slot_id, item->details.slot_id, bag->details.num_slots);
+					lua_interface->SetLuaUserDataStale(item);
 					safe_delete(item);
 					return false;
 				}
@@ -3235,6 +3236,8 @@ void PlayerItemList::RemoveItem(Item* item, bool delete_item){
 		map<int32, Item*>::iterator itr = indexed_items.find(item->details.index);
 		if(itr != indexed_items.end() && item == indexed_items[item->details.index])
 			indexed_items[item->details.index] = 0;
+		
+		lua_interface->SetLuaUserDataStale(item);
 		safe_delete(item);
 	}
 	MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
@@ -3257,6 +3260,8 @@ void PlayerItemList::DestroyItem(int16 index){
 	if(item && items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0)
 		items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id);
 	indexed_items[index] = 0;
+	
+	lua_interface->SetLuaUserDataStale(item);
 	safe_delete(item);
 	MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
 }

+ 3 - 0
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -28,9 +28,11 @@
 #include "Items.h"
 #include "../World.h"
 #include "../Rules/Rules.h"
+#include "../LuaInterface.h"
 
 extern World world;
 extern RuleManager rule_manager;
+extern LuaInterface* lua_interface;
 
 // handle new database class til all functions are converted
 void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item) 
@@ -1310,6 +1312,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
 					if(timeInSeconds >= rule_manager.GetGlobalRule(R_Player, TemporaryItemLogoutTime)->GetFloat()) {
 						DeleteItem(char_id, item, 0);
 						LogWrite(ITEM__INFO, 0, "Items", "\tCharacter ID %u had a temporary item %s which was removed due to time limit.", char_id, item->name.c_str());
+						lua_interface->SetLuaUserDataStale(item);
 						safe_delete(item);
 						continue;
 					}

+ 5 - 2
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -896,6 +896,7 @@ int EQ2Emu_lua_RemoveLootItem(lua_State* state) {
 	if (spawn && spawn->IsEntity()) {
 		int32 item_id = lua_interface->GetInt32Value(state, 2);
 		Item* item = spawn->LootItem(item_id);
+		lua_interface->SetLuaUserDataStale(item);
 		safe_delete(item);
 	}
 	lua_interface->ResetFunctionStack(state);
@@ -975,6 +976,7 @@ int EQ2Emu_lua_CreateConversation(lua_State* state) {
 		return 0;
 
 	vector<ConversationOption>* conversation = lua_interface->GetConversation(state);
+	lua_interface->SetLuaUserDataStale(conversation);
 	safe_delete(conversation);
 	lua_interface->ResetFunctionStack(state);
 
@@ -1075,8 +1077,8 @@ int EQ2Emu_lua_StartDialogConversation(lua_State* state) {
 			}
 		}
 	}
+	lua_interface->SetLuaUserDataStale(conversation);
 	safe_delete(conversation);
-	lua_interface->SetConversationValue(state, NULL);
 	return 0;
 }
 
@@ -1104,8 +1106,8 @@ int EQ2Emu_lua_StartConversation(lua_State* state) {
 			client->DisplayConversation(source, 1, conversation, text.c_str(), mp3.c_str(), key1, key2, language, can_close);
 		else
 			client->DisplayConversation(source, 1, conversation, text.c_str(), nullptr, 0, 0, language, can_close);
+		lua_interface->SetLuaUserDataStale(conversation);
 		safe_delete(conversation);
-		lua_interface->SetConversationValue(state, NULL);
 	}
 	else
 		LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in StartConversation, potentially AddConversationOption not yet called or the StartConversation arguments are incorrect, text: %s, conversationSize: %i.", source ? source->GetName() : "UNKNOWN", text.size() ? text.c_str() : "", conversation ? conversation->size() : -1);
@@ -5091,6 +5093,7 @@ int EQ2Emu_lua_SendOptionWindow(lua_State* state) {
 			i++;
 		}
 		client->QueuePacket(packet->serialize());
+		lua_interface->SetLuaUserDataStale(option_window);
 		safe_delete(option_window);
 		safe_delete(packet);
 	}

+ 74 - 18
EQ2/source/WorldServer/LuaInterface.cpp

@@ -46,7 +46,6 @@ LuaInterface::LuaInterface() {
 	MSpawnScripts.SetName("LuaInterface::MSpawnScripts");
 	MZoneScripts.SetName("LuaInterface::MZoneScripts");
 	MQuests.SetName("LuaInterface::MQuests");
-	MLUAUserData.SetName("LuaInterface::MLUAUserData");
 	MLUAMain.SetName("LuaInterface::MLUAMain");
 	MItemScripts.SetName("LuaInterface::MItemScripts");
 	MSpellDelete.SetName("LuaInterface::MSpellDelete");
@@ -463,10 +462,12 @@ Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, con
 		if(lua_pcall(state, 1, 0, 0) != 0){
 			LogError("Error processing Quest \"%s\" (%u): %s", name ? name : "unknown", id, lua_tostring(state, -1));
 			lua_pop(state, 1);
+			SetLuaUserDataStale(quest);
 			safe_delete(quest);
 			return 0;
 		}
 		if(!quest->GetName()){
+			SetLuaUserDataStale(quest);
 			safe_delete(quest);	
 			return 0;
 		}
@@ -812,7 +813,7 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 		lua_getglobal(spell->state, "remove");
 		LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper();
 		spawn_wrapper->spawn = spell->caster;
-		AddUserDataPtr(spawn_wrapper);
+		AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn);
 		lua_pushlightuserdata(spell->state, spawn_wrapper);
 		if(spell->caster && (spell->initial_target || spell->caster->GetTarget())){
 			spawn_wrapper = new LUASpawnWrapper();
@@ -820,7 +821,7 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 				spawn_wrapper->spawn = spell->caster->GetTarget();
 			else
 				spawn_wrapper->spawn = spell->caster->GetZone()->GetSpawnByID(spell->initial_target);
-			AddUserDataPtr(spawn_wrapper);
+			AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn);
 			lua_pushlightuserdata(spell->state, spawn_wrapper);
 		}
 		else
@@ -1514,10 +1515,12 @@ void LuaInterface::ResetFunctionStack(lua_State* state) {
 	lua_settop(state, 0);
 }
 
-void LuaInterface::AddUserDataPtr(LUAUserData* data) {
-	MLUAUserData.lock();
+void LuaInterface::AddUserDataPtr(LUAUserData* data, void* data_ptr) {
+	std::unique_lock lock(MLUAUserData);
+	if(data_ptr) {
+		user_data_ptr[data_ptr] = data;
+	}
 	user_data[data] = Timer::GetCurrentTime2() + 300000; //allow a function to use this pointer for 5 minutes
-	MLUAUserData.unlock();
 }
 
 void LuaInterface::DeletePendingSpells(bool all) {
@@ -1558,6 +1561,7 @@ void LuaInterface::DeletePendingSpells(bool all) {
 				safe_delete(spell->spell);
 			}
 
+			SetLuaUserDataStale(spell);
 			safe_delete(spell);
 		}
 	}
@@ -1575,7 +1579,7 @@ void LuaInterface::DeletePendingSpell(LuaSpell* spell) {
 }
 
 void LuaInterface::DeleteUserDataPtrs(bool all) {
-	MLUAUserData.lock();
+	std::unique_lock lock(MLUAUserData);
 	if(user_data.size() > 0){
 		map<LUAUserData*, int32>::iterator itr;
 		int32 time = Timer::GetCurrentTime2();
@@ -1588,14 +1592,49 @@ void LuaInterface::DeleteUserDataPtrs(bool all) {
 		LUAUserData* data = 0;
 		for(del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++){
 			data = *del_itr;
+			
+			void* target = 0;
+			if(data->IsConversationOption()) {
+				target = data->conversation_options;
+			}
+			else if(data->IsSpawnList()) {
+				target = data->spawn_list;
+			}
+			else if(data->IsOptionWindow()) {
+				target = data->option_window_option;
+			}
+			else if(data->IsSpawn()) {
+				target = data->spawn;
+			}
+			else if(data->IsQuest()) {
+				target = data->quest;
+			}
+			else if(data->IsZone()) {
+				target = data->zone;
+			}
+			else if(data->IsItem()) {
+				target = data->item;
+			}
+			else if(data->IsSkill()) {
+				target = data->skill;
+			}
+			else if(data->IsSpell()) {
+				target = data->spell;
+			}
+			if(target) {
+				std::map<void*, LUAUserData*>::iterator itr = user_data_ptr.find(target);
+				if(itr != user_data_ptr.end()) {
+					user_data_ptr.erase(itr);
+				}
+			}
 			user_data.erase(data);
 			safe_delete(data);
 		}
 	}
-	MLUAUserData.unlock();
 }
 
 Spawn* LuaInterface::GetSpawn(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	Spawn* ret = 0;
 	if (lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1615,6 +1654,7 @@ Spawn* LuaInterface::GetSpawn(lua_State* state, int8 arg_num) {
 }
 
 vector<ConversationOption>*	LuaInterface::GetConversation(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	vector<ConversationOption>* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1634,6 +1674,7 @@ vector<ConversationOption>*	LuaInterface::GetConversation(lua_State* state, int8
 }
 
 vector<Spawn*>* LuaInterface::GetSpawnList(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	vector<Spawn*>* ret = 0;
 	if (lua_islightuserdata(state, arg_num)) {
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1653,6 +1694,7 @@ vector<Spawn*>* LuaInterface::GetSpawnList(lua_State* state, int8 arg_num) {
 }
 
 vector<OptionWindowOption>*	LuaInterface::GetOptionWindow(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	vector<OptionWindowOption>* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1672,6 +1714,7 @@ vector<OptionWindowOption>*	LuaInterface::GetOptionWindow(lua_State* state, int8
 }
 
 Quest* LuaInterface::GetQuest(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	Quest* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1691,6 +1734,7 @@ Quest* LuaInterface::GetQuest(lua_State* state, int8 arg_num) {
 }
 
 Item* LuaInterface::GetItem(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	Item* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1710,6 +1754,7 @@ Item* LuaInterface::GetItem(lua_State* state, int8 arg_num) {
 }
 
 Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	Skill* ret = 0;
 	if (lua_islightuserdata(state, arg_num)) {
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1729,6 +1774,7 @@ Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) {
 }
 
 LuaSpell* LuaInterface::GetSpell(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	LuaSpell* ret = 0;
 	if (lua_islightuserdata(state, arg_num)) {
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1748,6 +1794,7 @@ LuaSpell* LuaInterface::GetSpell(lua_State* state, int8 arg_num) {
 }
 
 ZoneServer* LuaInterface::GetZone(lua_State* state, int8 arg_num) {
+	std::shared_lock lock(MLUAUserData);
 	ZoneServer* ret = 0;
 	if(lua_islightuserdata(state, arg_num)){
 		LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num);
@@ -1867,63 +1914,63 @@ void LuaInterface::SetSInt64Value(lua_State* state, sint64 value) {
 void LuaInterface::SetSpawnValue(lua_State* state, Spawn* spawn) {
 	LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper();
 	spawn_wrapper->spawn = spawn;
-	AddUserDataPtr(spawn_wrapper);
+	AddUserDataPtr(spawn_wrapper, spawn);
 	lua_pushlightuserdata(state, spawn_wrapper);
 }
 
 void LuaInterface::SetSpawnListValue(lua_State* state, vector<Spawn*>* spawnList) {
 	LUASpawnListWrapper* spawnList_wrapper = new LUASpawnListWrapper();
 	spawnList_wrapper->spawn_list = spawnList;
-	AddUserDataPtr(spawnList_wrapper);
+	AddUserDataPtr(spawnList_wrapper, spawnList);
 	lua_pushlightuserdata(state, spawnList_wrapper);
 }
 
 void LuaInterface::SetConversationValue(lua_State* state, vector<ConversationOption>* conversation) {
 	LUAConversationOptionWrapper* conv_wrapper = new LUAConversationOptionWrapper();
 	conv_wrapper->conversation_options = conversation;
-	AddUserDataPtr(conv_wrapper);
+	AddUserDataPtr(conv_wrapper, conversation);
 	lua_pushlightuserdata(state, conv_wrapper);
 }
 
 void LuaInterface::SetOptionWindowValue(lua_State* state, vector<OptionWindowOption>* optionWindow) {
 	LUAOptionWindowWrapper* option_wrapper = new LUAOptionWindowWrapper();
 	option_wrapper->option_window_option = optionWindow;
-	AddUserDataPtr(option_wrapper);
+	AddUserDataPtr(option_wrapper, optionWindow);
 	lua_pushlightuserdata(state, option_wrapper);
 }
 
 void LuaInterface::SetItemValue(lua_State* state, Item* item) {
 	LUAItemWrapper* item_wrapper = new LUAItemWrapper();
 	item_wrapper->item = item;
-	AddUserDataPtr(item_wrapper);
+	AddUserDataPtr(item_wrapper, item);
 	lua_pushlightuserdata(state, item_wrapper);
 }
 
 void LuaInterface::SetSkillValue(lua_State* state, Skill* skill) {
 	LUASkillWrapper* skill_wrapper = new LUASkillWrapper();
 	skill_wrapper->skill = skill;
-	AddUserDataPtr(skill_wrapper);
+	AddUserDataPtr(skill_wrapper, skill);
 	lua_pushlightuserdata(state, skill_wrapper);
 }
 
 void LuaInterface::SetQuestValue(lua_State* state, Quest* quest) {
 	LUAQuestWrapper* quest_wrapper = new LUAQuestWrapper();
 	quest_wrapper->quest = quest;
-	AddUserDataPtr(quest_wrapper);
+	AddUserDataPtr(quest_wrapper, quest);
 	lua_pushlightuserdata(state, quest_wrapper);
 }
 
 void LuaInterface::SetZoneValue(lua_State* state, ZoneServer* zone) {
 	LUAZoneWrapper* zone_wrapper = new LUAZoneWrapper();
 	zone_wrapper->zone = zone;
-	AddUserDataPtr(zone_wrapper);
+	AddUserDataPtr(zone_wrapper, zone);
 	lua_pushlightuserdata(state, zone_wrapper);
 }
 
 void LuaInterface::SetSpellValue(lua_State* state, LuaSpell* spell) {
 	LUASpellWrapper* spell_wrapper = new LUASpellWrapper();
 	spell_wrapper->spell = spell;
-	AddUserDataPtr(spell_wrapper);
+	AddUserDataPtr(spell_wrapper, spell);
 	lua_pushlightuserdata(state, spell_wrapper);
 }
 
@@ -2538,6 +2585,15 @@ int32 LuaInterface::GetFreeCustomSpellID()
 	return id;
 }
 
+
+void LuaInterface::SetLuaUserDataStale(void* ptr) {
+	std::unique_lock lock(MLUAUserData);
+	std::map<void*, LUAUserData*>::iterator itr = user_data_ptr.find(ptr);
+	if(itr != user_data_ptr.end()) {
+		itr->second->correctly_initialized = false;
+	}
+}
+
 LUAUserData::LUAUserData(){
 	correctly_initialized = false;
 	quest = 0;
@@ -2660,4 +2716,4 @@ LUASpellWrapper::LUASpellWrapper() {
 
 bool LUASpellWrapper::IsSpell() {
 	return true;
-}
+}

+ 10 - 2
EQ2/source/WorldServer/LuaInterface.h

@@ -20,6 +20,9 @@
 #ifndef LUA_INTERFACE_H
 #define LUA_INTERFACE_H
 
+#include <mutex>
+#include <shared_mutex>
+
 #include "Spawn.h"
 #include "Spells.h"
 #include "../common/Mutex.h"
@@ -281,7 +284,7 @@ public:
 	void			UpdateDebugClients(Client* client);
 	void			ProcessErrorMessage(const char* message);
 	map<Client*, int32> GetDebugClients(){ return debug_clients; }
-	void			AddUserDataPtr(LUAUserData* data);
+	void			AddUserDataPtr(LUAUserData* data, void* data_ptr = 0);
 	void			DeleteUserDataPtrs(bool all);
 	void			DeletePendingSpells(bool all);
 	void			DeletePendingSpell(LuaSpell* spell);
@@ -304,6 +307,9 @@ public:
 	LuaSpell*		FindCustomSpell(int32 id);
 
 	int32			GetFreeCustomSpellID();
+	
+	void			SetLuaUserDataStale(void* ptr);
+	
 private:
 	bool			shutting_down;
 	bool			lua_system_reloading;
@@ -311,6 +317,7 @@ private:
 	Timer*			user_data_timer;
 	Timer*			spell_delete_timer;
 	map<LUAUserData*, int32> user_data;
+	map<void*, LUAUserData*> user_data_ptr;
 	map<Client*, int32>	debug_clients;
 	map<lua_State*, LuaSpell*> current_spells;
 	vector<string>*	GetDirectoryListing(const char* directory);
@@ -346,10 +353,11 @@ private:
 	Mutex			MItemScripts;
 	Mutex			MZoneScripts;
 	Mutex			MQuests;
-	Mutex			MLUAUserData;
 	Mutex			MLUAMain;
 	Mutex			MSpellDelete;
 	Mutex			MCustomSpell;
 	Mutex			MRegionScripts;
+	
+	mutable std::shared_mutex	MLUAUserData;
 };
 #endif

+ 2 - 0
EQ2/source/WorldServer/SpellProcess.cpp

@@ -2885,6 +2885,8 @@ void SpellProcess::DeleteSpell(LuaSpell* spell)
 		lua_interface->RemoveCustomSpell(spell->spell->GetSpellID());
 		safe_delete(spell->spell);
 	}
+	
+	lua_interface->SetLuaUserDataStale(spell);
 	safe_delete(spell);
 }
 

+ 1 - 0
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -2736,6 +2736,7 @@ void WorldDatabase::LoadCharacterQuests(Client* client){
 					// so get a fresh quest object to add as an active quest
 					if (given_timestamp > completed_timestamp)
 					{
+						lua_interface->SetLuaUserDataStale(quest);
 						safe_delete(quest);
 						quest = master_quest_list.GetQuest(atoul(row[0]));
 					}

+ 50 - 37
EQ2/source/WorldServer/client.cpp

@@ -204,7 +204,6 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125
 	client_reloading_zone = false;
 	last_saved_timestamp = 0;
 	MQueueStateCmds.SetName("Client::MQueueStateCmds");
-	MConversation.SetName("Client::MConversation");
 	save_spell_state_timer.Disable();
 	save_spell_state_time_bucket = 0;
 	player_loading_complete = false;
@@ -1319,32 +1318,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DialogSelectMsg", opcode, opcode);
 		PacketStruct* packet = configReader.getStruct("WS_DialogSelect", GetVersion());
 		if (packet) {
-			packet->LoadPacketData(app->pBuffer, app->size);
-			int32 conversation_id = packet->getType_int32_ByName("conversation_id");
-			int32 response_index = packet->getType_int32_ByName("response");
-			if (GetCurrentZone()) {
-				MConversation.readlock();
-				int32 spawn_id = conversation_spawns[conversation_id];
-				Spawn* spawn = nullptr;
-				if(spawn_id) {
-					spawn = GetCurrentZone()->GetSpawnByID(spawn_id);
-				}
-				
-				Item* item = conversation_items[conversation_id];
-				MConversation.releasereadlock();
-				if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) {
-					if (spawn)
-						GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CONVERSATION, player, conversation_map[conversation_id][response_index].c_str());
-					else if (item && lua_interface && item->GetItemScript())
-						lua_interface->RunItemScript(item->GetItemScript(), conversation_map[conversation_id][response_index].c_str(), item, player);
-					else
-						CloseDialog(conversation_id);
-				}
-				else
-					CloseDialog(conversation_id);
+				packet->LoadPacketData(app->pBuffer, app->size);
+				int32 conversation_id = packet->getType_int32_ByName("conversation_id");
+				int32 response_index = packet->getType_int32_ByName("response");
+				HandleDialogSelectMsg(conversation_id, response_index);
 			}
 			safe_delete(packet);
-		}
 		break;
 	}
 	case OP_CancelMoveObjectModeMsg: {
@@ -6511,6 +6490,7 @@ void Client::GiveQuestReward(Quest* quest, bool has_displayed) {
 }
 
 void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) {
+	std::unique_lock lock(MConversation);
 	PacketStruct* packet = configReader.getStruct("WS_DialogOpen", GetVersion());
 	if (packet) {
 		packet->setDataByName("conversation_id", conversation_id);
@@ -6547,9 +6527,9 @@ void Client::DisplayConversation(Item* item, vector<ConversationOption>* convers
 		next_conversation_id++;
 		conversation_id = next_conversation_id;
 	}
-	MConversation.writelock();
+	MConversation.lock();
 	conversation_items[conversation_id] = item;
-	MConversation.releasewritelock();
+	MConversation.unlock();
 	if (type == 4)
 		DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2, language, can_close);
 	else
@@ -6566,9 +6546,9 @@ void Client::DisplayConversation(Spawn* src, int8 type, vector<ConversationOptio
 		next_conversation_id++;
 		conversation_id = next_conversation_id;
 	}
-	MConversation.writelock();
+	MConversation.lock();
 	conversation_spawns[conversation_id] = src->GetID();
-	MConversation.releasewritelock();
+	MConversation.unlock();
 
 	/* Spawns can start two different types of conversations.
 	 * Type 1: The chat type with bubbles.
@@ -6583,6 +6563,7 @@ void Client::DisplayConversation(Spawn* src, int8 type, vector<ConversationOptio
 }
 
 void Client::CloseDialog(int32 conversation_id) {
+	std::unique_lock lock(MConversation);
 	PacketStruct* packet = configReader.getStruct("WS_ServerDialogClose", GetVersion());
 	if (packet) {
 		packet->setDataByName("conversation_id", conversation_id);
@@ -6590,7 +6571,6 @@ void Client::CloseDialog(int32 conversation_id) {
 		safe_delete(packet);
 	}
 
-	MConversation.writelock();
 	std::map<int32, Item*>::iterator itr;
 	while((itr = conversation_items.find(conversation_id)) != conversation_items.end())
 	{
@@ -6603,13 +6583,11 @@ void Client::CloseDialog(int32 conversation_id) {
 	{
 		conversation_spawns.erase(itr2);
 	}
-	MConversation.releasewritelock();
-
 }
 
 int32 Client::GetConversationID(Spawn* spawn, Item* item) {
+	std::shared_lock lock(MConversation);
 	int32 conversation_id = 0;
-	MConversation.readlock();
 	if (spawn) {
 		map<int32, int32>::iterator itr;
 		for (itr = conversation_spawns.begin(); itr != conversation_spawns.end(); itr++) {
@@ -6628,7 +6606,6 @@ int32 Client::GetConversationID(Spawn* spawn, Item* item) {
 			}
 		}
 	}
-	MConversation.releasereadlock();
 
 	return conversation_id;
 }
@@ -6756,6 +6733,7 @@ bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) {
 			lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
 	}
 	else {
+		lua_interface->SetLuaUserDataStale(item);
 		// likely lore conflict
 		safe_delete(item);
 
@@ -6805,6 +6783,7 @@ bool Client::AddItemToBank(Item* item) {
 			lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
 	}
 	else {
+		lua_interface->SetLuaUserDataStale(item);
 		// likely lore conflict
 		safe_delete(item);
 		return false;
@@ -6857,6 +6836,7 @@ bool Client::RemoveItem(Item* item, int16 quantity, bool force_override_no_delet
 		if (delete_item)
 		{
 			PurgeItem(item);
+			lua_interface->SetLuaUserDataStale(item);
 			safe_delete(item);
 		}
 		return true;
@@ -7106,8 +7086,10 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
 			else
 				SimpleMessage(CHANNEL_COLOR_RED, "You cannot afford this item.");
 
-			if(!itemAdded && !itemDeleted)
+			if(!itemAdded && !itemDeleted) {
+				lua_interface->SetLuaUserDataStale(item);
 				safe_delete(item);
+			}
 		}
 	}
 
@@ -7166,6 +7148,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
 			item->details.count = quantity;
 			if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) {
 				SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
+				lua_interface->SetLuaUserDataStale(item);
 				safe_delete(item);
 			}
 			else {
@@ -11062,7 +11045,7 @@ void Client::ProcessStateCommands()
 
 void Client::PurgeItem(Item* item)
 {
-	MConversation.writelock();
+	std::unique_lock lock(MConversation);
 	map<int32, Item*>::iterator itr;
 	for(itr = conversation_items.begin(); itr != conversation_items.end(); itr++)
 	{
@@ -11072,7 +11055,6 @@ void Client::PurgeItem(Item* item)
 			break;
 		}
 	}
-	MConversation.releasewritelock();
 }
 
 void Client::ConsumeFoodDrink(Item* item, int32 slot)
@@ -11404,4 +11386,35 @@ bool Client::SetPlayerPOVGhost(Spawn* spawn) {
 	}
 	
 	return false;
+}
+
+void Client::HandleDialogSelectMsg(int32 conversation_id, int32 response_index) {
+	std::string conversation = "";
+	MConversation.lock_shared();
+	if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) {
+		conversation = std::string(conversation_map[conversation_id][response_index].c_str());
+	}
+	
+	int32 spawn_id = conversation_spawns[conversation_id];
+	
+	Item* item = conversation_items[conversation_id];
+	MConversation.unlock_shared();
+	
+	if (GetCurrentZone()) {
+		Spawn* spawn = nullptr;
+		if(spawn_id) {
+			spawn = GetCurrentZone()->GetSpawnByID(spawn_id);
+		}
+		
+		if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) {
+			if (spawn)
+				GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CONVERSATION, player, conversation.c_str());
+			else if (item && lua_interface && item->GetItemScript())
+				lua_interface->RunItemScript(item->GetItemScript(), conversation.c_str(), item, player);
+			else
+				CloseDialog(conversation_id);
+		}
+		else
+			CloseDialog(conversation_id);
+	}				
 }

+ 5 - 1
EQ2/source/WorldServer/client.h

@@ -22,6 +22,8 @@
 
 #include <list>
 #include <atomic>
+#include <mutex>
+#include <shared_mutex>
 
 #include "../common/EQStream.h"
 #include "../common/timer.h"
@@ -571,6 +573,8 @@ public:
 	bool	SetPlayerPOVGhost(Spawn* spawn);
 	
 	int32	GetPlayerPOVGhostSpawnID() { return pov_ghost_spawn_id; }
+	
+	void	HandleDialogSelectMsg(int32 conversation_id, int32 response_index);
 private:
 	void    SavePlayerImages();
 	void	SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
@@ -601,7 +605,7 @@ private:
 	int32	next_conversation_id;
 	map<int32, int32> conversation_spawns;
 	map<int32, Item*> conversation_items;
-	Mutex MConversation;
+	mutable std::shared_mutex MConversation;
 	map<int32, map<int8, string> > conversation_map;
 	int32	current_quest_id;
 	Spawn*	banker;

+ 2 - 0
EQ2/source/WorldServer/zoneserver.cpp

@@ -1298,6 +1298,8 @@ void ZoneServer::DeleteSpawns(bool delete_all) {
 				
 				MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
 			
+				lua_interface->SetLuaUserDataStale(spawn);
+				
 				safe_delete(spawn);
 			}
 			else