From 0fbdb1dad24be2aa9b5efff7a4c8014e29158f45 Mon Sep 17 00:00:00 2001 From: dongresource Date: Mon, 8 Mar 2021 22:29:21 +0100 Subject: [PATCH] Improve sanity checks when opening crates and combining items And ignore ITEM_MOVE packets while trading. --- src/ItemManager.cpp | 50 ++++++++++++++++++++++++++++----------------- src/ItemManager.hpp | 8 ++++++-- src/NPCManager.cpp | 30 +++++++++++++++++---------- src/TableData.cpp | 1 + 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index d5620aa..b3e9133 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -58,6 +58,11 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) { return; // NOTE: sending a no-op, "move in-place" packet is not necessary + if (plr->isTrading) { + std::cout << "[WARN] Player attempted to move item while trading" << std::endl; + return; + } + // get the fromItem sItemBase *fromItem; switch ((SlotType)itemmove->eFrom) { @@ -804,24 +809,31 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { if (data->size != sizeof(sP_CL2FE_REQ_ITEM_CHEST_OPEN)) return; // ignore the malformed packet - sP_CL2FE_REQ_ITEM_CHEST_OPEN *chest = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf; + sP_CL2FE_REQ_ITEM_CHEST_OPEN *pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf; // sanity check - if (chest->ChestItem.iType != 9) { + if (pkt->eIL != 1 || pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT) + return; + + Player *plr = PlayerManager::getPlayer(sock); + + sItemBase *chest = &plr->Inven[pkt->iSlotNum]; + // we could reject the packet if the client thinks the item is different, but eh + + if (chest->iType != 9) { std::cout << "[WARN] Player tried to open a crate with incorrect iType ?!" << std::endl; return; } #ifdef ACADEMY // check if chest isn't a nano capsule - if (NanoCapsules.find(chest->ChestItem.iID) != NanoCapsules.end()) - return nanoCapsuleHandler(sock, chest); + if (NanoCapsules.find(chest->iID) != NanoCapsules.end()) + return nanoCapsuleHandler(sock, pkt->iSlotNum, chest); #endif - Player *plr = PlayerManager::getPlayer(sock); // chest opening acknowledgement packet INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); - resp.iSlotNum = chest->iSlotNum; + resp.iSlotNum = pkt->iSlotNum; // item giving packet const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); @@ -844,21 +856,21 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { reward->m_iBatteryN = plr->batteryN; reward->m_iBatteryW = plr->batteryW; - item->iSlotNum = chest->iSlotNum; - item->eIL = chest->eIL; + item->iSlotNum = pkt->iSlotNum; + item->eIL = 1; int itemSetId = -1, rarity = -1, ret = -1; bool failing = false; // find the crate - if (Crates.find(chest->ChestItem.iID) == Crates.end()) { - std::cout << "[WARN] Crate " << chest->ChestItem.iID << " not found!" << std::endl; + if (Crates.find(chest->iID) == Crates.end()) { + std::cout << "[WARN] Crate " << chest->iID << " not found!" << std::endl; failing = true; } - Crate& crate = Crates[chest->ChestItem.iID]; + Crate& crate = Crates[chest->iID]; if (!failing) - itemSetId = getItemSetId(crate, chest->ChestItem.iID); + itemSetId = getItemSetId(crate, chest->iID); if (itemSetId == -1) failing = true; @@ -879,7 +891,7 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { item->sItem.iOpt = 1; } // update player - plr->Inven[chest->iSlotNum] = item->sItem; + plr->Inven[pkt->iSlotNum] = item->sItem; // transmit item sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); @@ -1064,13 +1076,13 @@ void ItemManager::updateEquips(CNSocket* sock, Player* plr) { } #ifdef ACADEMY -void ItemManager::nanoCapsuleHandler(CNSocket* sock, sP_CL2FE_REQ_ITEM_CHEST_OPEN* chest) { +void ItemManager::nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { Player* plr = PlayerManager::getPlayer(sock); - int32_t nanoId = NanoCapsules[chest->ChestItem.iID]; + int32_t nanoId = NanoCapsules[chest->iID]; // chest opening acknowledgement packet INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); - resp.iSlotNum = chest->iSlotNum; + resp.iSlotNum = slot; // in order to remove capsule form inventory, we have to send item reward packet with empty item const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); @@ -1093,11 +1105,11 @@ void ItemManager::nanoCapsuleHandler(CNSocket* sock, sP_CL2FE_REQ_ITEM_CHEST_OPE reward->m_iBatteryN = plr->batteryN; reward->m_iBatteryW = plr->batteryW; - item->iSlotNum = chest->iSlotNum; - item->eIL = chest->eIL; + item->iSlotNum = slot; + item->eIL = 1; // update player serverside - plr->Inven[chest->iSlotNum] = item->sItem; + plr->Inven[slot] = item->sItem; // transmit item sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 4feede8..f3c22c6 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -23,7 +23,11 @@ namespace ItemManager { }; struct Item { bool tradeable, sellable; - int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, fireRate, defense, gender; // TODO: implement more as needed + int buyPrice, sellPrice; + int stackSize, level, rarity; + int pointDamage, groupDamage, fireRate, defense, gender; + int weaponType; + // TODO: implement more as needed }; // hopefully this is fine since it's never modified after load extern std::map, Item> ItemData; // -> data @@ -69,6 +73,6 @@ namespace ItemManager { #ifdef ACADEMY extern std::map NanoCapsules; // crate id -> nano id - void nanoCapsuleHandler(CNSocket* sock, sP_CL2FE_REQ_ITEM_CHEST_OPEN* chest); + void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest); #endif } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 562a6b6..9458cb7 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -361,13 +361,16 @@ void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf; Player* plr = PlayerManager::getPlayer(sock); - if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1 - INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); - failResp.iCostumeItemSlot = req->iCostumeItemSlot; - failResp.iStatItemSlot = req->iStatItemSlot; - failResp.iErrorCode = 0; - sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + // prepare fail packet + INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); + failResp.iCostumeItemSlot = req->iCostumeItemSlot; + failResp.iStatItemSlot = req->iStatItemSlot; + failResp.iErrorCode = 0; + + // sanity check slot indices + if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); return; } @@ -376,17 +379,22 @@ void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) { ItemManager::Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType); ItemManager::Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType); + // sanity check item and combination entry existence if (itemStatsDat == nullptr || itemLooksDat == nullptr - || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { // sanity check 2 - INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); - failResp.iCostumeItemSlot = req->iCostumeItemSlot; - failResp.iStatItemSlot = req->iStatItemSlot; - failResp.iErrorCode = 0; + || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl; sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); return; } + // sanity check matching item types + if (itemStats->iType != itemLooks->iType + || (itemStats->iType == 0 && itemStatsDat->weaponType != itemLooksDat->weaponType)) { + std::cout << "[WARN] Player attempted to combine mismatched items" << std::endl; + sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL)); + return; + } + CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)]; int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks; float successChance = recipe->base / 100.0f; // base success chance diff --git a/src/TableData.cpp b/src/TableData.cpp index b96aad4..6a63b11 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -160,6 +160,7 @@ void TableData::init() { itemData.fireRate = item["m_iDelayTime"]; itemData.defense = item["m_iDefenseRat"]; itemData.gender = item["m_iReqSex"]; + itemData.weaponType = item["m_iEquipType"]; } else { itemData.rarity = 1; }