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

View File

@ -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<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
@ -69,6 +73,6 @@ namespace ItemManager {
#ifdef ACADEMY
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
}

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

View File

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