Improve sanity checks when opening crates and combining items

And ignore ITEM_MOVE packets while trading.
This commit is contained in:
dongresource 2021-03-08 22:29:21 +01:00
parent d781fae3ba
commit 0fbdb1dad2
4 changed files with 57 additions and 32 deletions

View File

@ -58,6 +58,11 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
return; return;
// NOTE: sending a no-op, "move in-place" packet is not necessary // 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 // get the fromItem
sItemBase *fromItem; sItemBase *fromItem;
switch ((SlotType)itemmove->eFrom) { 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)) if (data->size != sizeof(sP_CL2FE_REQ_ITEM_CHEST_OPEN))
return; // ignore the malformed packet 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 // 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; std::cout << "[WARN] Player tried to open a crate with incorrect iType ?!" << std::endl;
return; return;
} }
#ifdef ACADEMY #ifdef ACADEMY
// check if chest isn't a nano capsule // check if chest isn't a nano capsule
if (NanoCapsules.find(chest->ChestItem.iID) != NanoCapsules.end()) if (NanoCapsules.find(chest->iID) != NanoCapsules.end())
return nanoCapsuleHandler(sock, chest); return nanoCapsuleHandler(sock, pkt->iSlotNum, chest);
#endif #endif
Player *plr = PlayerManager::getPlayer(sock);
// chest opening acknowledgement packet // chest opening acknowledgement packet
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
resp.iSlotNum = chest->iSlotNum; resp.iSlotNum = pkt->iSlotNum;
// item giving packet // item giving packet
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); 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_iBatteryN = plr->batteryN;
reward->m_iBatteryW = plr->batteryW; reward->m_iBatteryW = plr->batteryW;
item->iSlotNum = chest->iSlotNum; item->iSlotNum = pkt->iSlotNum;
item->eIL = chest->eIL; item->eIL = 1;
int itemSetId = -1, rarity = -1, ret = -1; int itemSetId = -1, rarity = -1, ret = -1;
bool failing = false; bool failing = false;
// find the crate // find the crate
if (Crates.find(chest->ChestItem.iID) == Crates.end()) { if (Crates.find(chest->iID) == Crates.end()) {
std::cout << "[WARN] Crate " << chest->ChestItem.iID << " not found!" << std::endl; std::cout << "[WARN] Crate " << chest->iID << " not found!" << std::endl;
failing = true; failing = true;
} }
Crate& crate = Crates[chest->ChestItem.iID]; Crate& crate = Crates[chest->iID];
if (!failing) if (!failing)
itemSetId = getItemSetId(crate, chest->ChestItem.iID); itemSetId = getItemSetId(crate, chest->iID);
if (itemSetId == -1) if (itemSetId == -1)
failing = true; failing = true;
@ -879,7 +891,7 @@ void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) {
item->sItem.iOpt = 1; item->sItem.iOpt = 1;
} }
// update player // update player
plr->Inven[chest->iSlotNum] = item->sItem; plr->Inven[pkt->iSlotNum] = item->sItem;
// transmit item // transmit item
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
@ -1064,13 +1076,13 @@ void ItemManager::updateEquips(CNSocket* sock, Player* plr) {
} }
#ifdef ACADEMY #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); Player* plr = PlayerManager::getPlayer(sock);
int32_t nanoId = NanoCapsules[chest->ChestItem.iID]; int32_t nanoId = NanoCapsules[chest->iID];
// chest opening acknowledgement packet // chest opening acknowledgement packet
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); 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 // 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); 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_iBatteryN = plr->batteryN;
reward->m_iBatteryW = plr->batteryW; reward->m_iBatteryW = plr->batteryW;
item->iSlotNum = chest->iSlotNum; item->iSlotNum = slot;
item->eIL = chest->eIL; item->eIL = 1;
// update player serverside // update player serverside
plr->Inven[chest->iSlotNum] = item->sItem; plr->Inven[slot] = item->sItem;
// transmit item // transmit item
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);

View File

@ -23,7 +23,11 @@ namespace ItemManager {
}; };
struct Item { struct Item {
bool tradeable, sellable; 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 // hopefully this is fine since it's never modified after load
extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
@ -69,6 +73,6 @@ namespace ItemManager {
#ifdef ACADEMY #ifdef ACADEMY
extern std::map<int32_t, int32_t> NanoCapsules; // crate id -> nano id extern std::map<int32_t, int32_t> 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 #endif
} }

View File

@ -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; sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1 // prepare fail packet
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp); INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
failResp.iCostumeItemSlot = req->iCostumeItemSlot; failResp.iCostumeItemSlot = req->iCostumeItemSlot;
failResp.iStatItemSlot = req->iStatItemSlot; failResp.iStatItemSlot = req->iStatItemSlot;
failResp.iErrorCode = 0; failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
// 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; 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; return;
} }
@ -376,17 +379,22 @@ void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) {
ItemManager::Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType); ItemManager::Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType);
ItemManager::Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType); ItemManager::Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType);
// sanity check item and combination entry existence
if (itemStatsDat == nullptr || itemLooksDat == nullptr if (itemStatsDat == nullptr || itemLooksDat == nullptr
|| ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { // sanity check 2 || ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) {
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
failResp.iStatItemSlot = req->iStatItemSlot;
failResp.iErrorCode = 0;
std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl; 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)); sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
return; 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)]; CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)];
int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks; int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks;
float successChance = recipe->base / 100.0f; // base success chance float successChance = recipe->base / 100.0f; // base success chance

View File

@ -160,6 +160,7 @@ void TableData::init() {
itemData.fireRate = item["m_iDelayTime"]; itemData.fireRate = item["m_iDelayTime"];
itemData.defense = item["m_iDefenseRat"]; itemData.defense = item["m_iDefenseRat"];
itemData.gender = item["m_iReqSex"]; itemData.gender = item["m_iReqSex"];
itemData.weaponType = item["m_iEquipType"];
} else { } else {
itemData.rarity = 1; itemData.rarity = 1;
} }