From 13e10b315db9c76da0347057d27d910ec58aa66a Mon Sep 17 00:00:00 2001 From: Emagi Date: Tue, 6 May 2025 18:38:43 -0400 Subject: [PATCH] KoS and earllier client fixes + server exploit fix - Can now use equipped charms (previously did not work had to use from inventory) - Fixed charges not decrement when equipped for all clients (they had unlimited charges!!) - Fixed click / interacting with ground spawns, doors, so on. Some objects may need a default setting since the older clients don't send the entity command where new clients do. Made a fallback structure WS_EntityVerbsVerbBackup for when that happens. --- server/WorldStructs.xml | 3 + source/WorldServer/Commands/Commands.cpp | 22 ++-- source/WorldServer/GroundSpawn.cpp | 5 +- source/WorldServer/client.cpp | 143 +++++++++++++---------- source/WorldServer/client.h | 2 +- 5 files changed, 99 insertions(+), 76 deletions(-) diff --git a/server/WorldStructs.xml b/server/WorldStructs.xml index ef8c4ae..2c3c035 100644 --- a/server/WorldStructs.xml +++ b/server/WorldStructs.xml @@ -3583,6 +3583,9 @@ to zero and treated like placeholders." /> + + + diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp index 02b4620..436b09f 100644 --- a/source/WorldServer/Commands/Commands.cpp +++ b/source/WorldServer/Commands/Commands.cpp @@ -2321,21 +2321,17 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie } case COMMAND_USE_EQUIPPED_ITEM:{ if (sep && sep->arg[0] && sep->IsNumber(0)){ - int32 slot_id = atoul(sep->arg[0]); + int16 slot_id = 0; + if(client->GetVersion() > 561) { + slot_id = atoul(sep->arg[0]); + } + else if(strlen(sep->argplus[0]) > 0) { // the way that the arguments are pulled the length is truncated, it will always be 2. So just try to convert what we did get in the string to an integer + const char* bufPtr = sep->argplus[0]; + slot_id = client->GetPlayer()->ConvertSlotFromClient(atoul(bufPtr), client->GetVersion()); + } Item* item = player->GetEquipmentList()->GetItem(slot_id); if (item && item->generic_info.usable && item->GetItemScript()) { - if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { - client->SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); - } - else if (item->CheckFlag(EVIL_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { - client->Message(0, "%s requires an evil race.", item->name.c_str()); - } - else if (item->CheckFlag(GOOD_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) { - client->Message(0, "%s requires a good race.", item->name.c_str()); - } - else { - lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, player->GetTarget()); - } + client->UseItem(item, player->GetTarget(), true, slot_id); } } break; diff --git a/source/WorldServer/GroundSpawn.cpp b/source/WorldServer/GroundSpawn.cpp index 75e99a7..e2798ef 100644 --- a/source/WorldServer/GroundSpawn.cpp +++ b/source/WorldServer/GroundSpawn.cpp @@ -552,7 +552,7 @@ string GroundSpawn::GetHarvestSpellName() { } void GroundSpawn::HandleUse(Client* client, string type){ - if(!client || type.length() == 0) + if(!client || (client->GetVersion() > 561 && type.length() == 0)) // older clients do not send the type return; //The following check disables the use of the groundspawn if spawn access is not granted if (client) { @@ -564,6 +564,9 @@ void GroundSpawn::HandleUse(Client* client, string type){ } MHarvestUse.lock(); + if(type == "" && client->GetVersion() <= 561) + type = GetHarvestSpellType(); + if (type == GetHarvestSpellType() && MeetsSpawnAccessRequirements(client->GetPlayer())) { Spell* spell = master_spell_list.GetSpellByName(GetHarvestSpellName().c_str()); if (spell) diff --git a/source/WorldServer/client.cpp b/source/WorldServer/client.cpp index 89566cc..4c3a9c8 100644 --- a/source/WorldServer/client.cpp +++ b/source/WorldServer/client.cpp @@ -1960,73 +1960,80 @@ bool Client::HandlePacket(EQApplicationPacket* app) { LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsVerbMsg", opcode, opcode); PacketStruct* packet = configReader.getStruct("WS_EntityVerbsVerb", GetVersion()); if (packet) { - if (packet->LoadPacketData(app->pBuffer, app->size)) { - int32 spawn_id = packet->getType_int32_ByName("spawn_id"); - Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); // fixed using GetTarget and the target was never set causing commands not to work - player->SetTarget(spawn); - if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) { - string command = packet->getType_EQ2_16BitString_ByName("command").data; + if(!packet->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(packet); + packet = configReader.getStruct("WS_EntityVerbsVerbBackup", GetVersion()); + if(!packet->LoadPacketData(app->pBuffer, app->size)) { + LogWrite(WORLD__ERROR, 0, "World", "Failed to properly load WS_EntityVerbsVerb for client."); + safe_delete(packet); + break; + } + } + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); // fixed using GetTarget and the target was never set causing commands not to work + player->SetTarget(spawn); + if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) { + string command = packet->getType_EQ2_16BitString_ByName("command").data; - if (!HandleHouseEntityCommands(spawn, spawn_id, command)) - { - if (EntityCommandPrecheck(spawn, command.c_str())) { - if (spawn->IsGroundSpawn()) - ((GroundSpawn*)spawn)->HandleUse(this, command); - else if (spawn->IsObject()) - ((Object*)spawn)->HandleUse(this, command); - else if (spawn->IsWidget()) - ((Widget*)spawn)->HandleUse(this, command); - else if (spawn->IsSign()) - ((Sign*)spawn)->HandleUse(this, command); - } + if (!HandleHouseEntityCommands(spawn, spawn_id, command)) + { + if (EntityCommandPrecheck(spawn, command.c_str())) { + if (spawn->IsGroundSpawn()) + ((GroundSpawn*)spawn)->HandleUse(this, command); + else if (spawn->IsObject()) + ((Object*)spawn)->HandleUse(this, command); + else if (spawn->IsWidget()) + ((Widget*)spawn)->HandleUse(this, command); + else if (spawn->IsSign()) + ((Sign*)spawn)->HandleUse(this, command); } } - else { - EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command"); - if (command.size > 0) { - string command_name = command.data; - if (command_name.find(" ") < 0xFFFFFFFF) { - if (GetVersion() <= 561) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant - string::size_type pos = command_name.find(" "); - while (pos != string::npos) { - command_name.replace(pos, 1, "_"); - pos = command_name.find(" "); - } + } + else { + EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command"); + if (command.size > 0) { + string command_name = command.data; + if (command_name.find(" ") < 0xFFFFFFFF) { + if (GetVersion() <= 561) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant + string::size_type pos = command_name.find(" "); + while (pos != string::npos) { + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); } - else - command_name = command_name.substr(0, command_name.find(" ")); } - int32 handler = commands.GetCommandHandler(command_name.c_str()); - if (handler != 0xFFFFFFFF) { - if (command.data == command_name) { - command.data = ""; - command.size = 0; - } - else { - command.data = command.data.substr(command.data.find(" ") + 1); - command.size = command.data.length(); - } - commands.Process(handler, &command, this); + else + command_name = command_name.substr(0, command_name.find(" ")); + } + int32 handler = commands.GetCommandHandler(command_name.c_str()); + if (handler != 0xFFFFFFFF) { + if (command.data == command_name) { + command.data = ""; + command.size = 0; } else { - if (spawn && spawn->IsNPC()) { - if (EntityCommandPrecheck(spawn, command.data.c_str())) { - if (!((NPC*)spawn)->HandleUse(this, command.data)) { - command_name = command.data; - string::size_type pos = command_name.find(" "); - while (pos != string::npos) { - command_name.replace(pos, 1, "_"); - pos = command_name.find(" "); - } - if (!((NPC*)spawn)->HandleUse(this, command_name)) { //convert the spaces to underscores and see if that makes a difference - LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); - } + command.data = command.data.substr(command.data.find(" ") + 1); + command.size = command.data.length(); + } + commands.Process(handler, &command, this); + } + else { + if (spawn && spawn->IsNPC()) { + if (EntityCommandPrecheck(spawn, command.data.c_str())) { + if (!((NPC*)spawn)->HandleUse(this, command.data)) { + command_name = command.data; + string::size_type pos = command_name.find(" "); + while (pos != string::npos) { + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); + } + if (!((NPC*)spawn)->HandleUse(this, command_name)) { //convert the spaces to underscores and see if that makes a difference + LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); } } } - else - LogWrite(WORLD__ERROR, 0, "World", "Unknown command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); } + else + LogWrite(WORLD__ERROR, 0, "World", "Unknown command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); } } } @@ -12819,7 +12826,7 @@ void Client::SetPlayer(Player* new_player) { player->SetClient(this); } -bool Client::UseItem(Item* item, Spawn* target) { +bool Client::UseItem(Item* item, Spawn* target, bool equippedItem, int16 equipSlot) { if (item && item->GetItemScript()) { int16 item_index = item->details.index; if (!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { @@ -12843,7 +12850,10 @@ bool Client::UseItem(Item* item, Spawn* target) { if (lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0) { //reobtain item make sure it wasn't removed - item = player->item_list.GetItemFromIndex(item_index); + if(equippedItem) + item = player->GetEquipmentList()->GetItem(equipSlot); + else + item = player->item_list.GetItemFromIndex(item_index); if (!item) { LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", GetPlayer()->GetName(), itemName.c_str(), item_id); return true; @@ -12857,10 +12867,21 @@ bool Client::UseItem(Item* item, Spawn* target) { { item->details.count--; // charges item->save_needed = true; - QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + if(equippedItem) { + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + QueuePacket(item->serialize(version, false, player)); + } + else { + QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + } if (!item->details.count) { - Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); - RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + if(equippedItem) { + Message(CHANNEL_NARRATIVE, "%s is out of charges.", item->name.c_str()); + } + else { + Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); + RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + } } return true; } diff --git a/source/WorldServer/client.h b/source/WorldServer/client.h index ee5e7c8..3f0b948 100644 --- a/source/WorldServer/client.h +++ b/source/WorldServer/client.h @@ -670,7 +670,7 @@ public: zoning_h = h; } - bool UseItem(Item* item, Spawn* target = nullptr); + bool UseItem(Item* item, Spawn* target = nullptr, bool equippedItem = false, int16 equipSlot = 0); void SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false); void SaveQuestRewardData(bool force_refresh = false);