1
0

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.
This commit is contained in:
Emagi 2025-05-06 18:38:43 -04:00
parent aad24ca1e4
commit 13e10b315d
5 changed files with 99 additions and 76 deletions

View File

@ -3583,6 +3583,9 @@ to zero and treated like placeholders." />
<Data ElementName="unknown2" Type="float" Size="1" />
<Data ElementName="unknown3" Type="float" Size="1" />
</Struct>
<Struct Name="WS_EntityVerbsVerbBackup" ClientVersion="1" OpcodeName="OP_EntityVerbsVerbMsg">
<Data ElementName="spawn_id" Type="int32" Size="1" />
</Struct>
<Struct Name="WS_EntityVerbsVerb" ClientVersion="1" OpcodeName="OP_EntityVerbsVerbMsg">
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="command" Type="EQ2_16Bit_String" Size="1" />

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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);