Merge pull request #55 from dongresource/combat1

Implemented combat, drops, crates and the guide changer
This commit is contained in:
2020-08-28 19:11:56 -05:00
committed by GitHub
20 changed files with 339 additions and 53 deletions

View File

@@ -123,23 +123,25 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
if (!alive)
return;
int tmpSize = size + sizeof(uint32_t);
uint8_t* tmpBuf = (uint8_t*)xmalloc(tmpSize);
size_t bodysize = size + sizeof(uint32_t);
uint8_t* fullpkt = (uint8_t*)xmalloc(bodysize+4);
uint8_t* body = fullpkt+4;
memcpy(fullpkt, (void*)&bodysize, 4);
// copy packet type to the front of the buffer & then the actual buffer
memcpy(tmpBuf, (void*)&type, sizeof(uint32_t));
memcpy(tmpBuf+sizeof(uint32_t), buf, size);
memcpy(body, (void*)&type, sizeof(uint32_t));
memcpy(body+sizeof(uint32_t), buf, size);
// encrypt the packet
switch (activeKey) {
case SOCKETKEY_E:
CNSocketEncryption::encryptData((uint8_t*)tmpBuf, (uint8_t*)(&EKey), tmpSize);
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&EKey), bodysize);
break;
case SOCKETKEY_FE:
CNSocketEncryption::encryptData((uint8_t*)tmpBuf, (uint8_t*)(&FEKey), tmpSize);
CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&FEKey), bodysize);
break;
default: {
free(tmpBuf);
free(fullpkt);
DEBUGLOG(
std::cout << "[WARN]: UNSET KEYTYPE FOR SOCKET!! ABORTING SEND" << std::endl;
)
@@ -147,15 +149,11 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
}
}
// send packet size
if (!sendData((uint8_t*)&tmpSize, sizeof(uint32_t)))
kill();
// send packet data!
if (alive && !sendData(tmpBuf, tmpSize))
if (alive && !sendData(fullpkt, bodysize+4))
kill();
free(tmpBuf); // free tmp buffer
free(fullpkt);
}
void CNSocket::setActiveKey(ACTIVEKEY key) {
@@ -172,7 +170,7 @@ void CNSocket::step() {
// we got out packet size!!!!
readSize = *((int32_t*)readBuffer);
// sanity check
if (readSize > MAX_PACKETSIZE) {
if (readSize > CN_PACKET_BUFFER_SIZE) {
kill();
return;
}

View File

@@ -1,6 +1,5 @@
#pragma once
#define MAX_PACKETSIZE 8192
#define DEBUGLOG(x) if (settings::VERBOSITY) {x};
#include <iostream>
@@ -56,7 +55,8 @@
[4 bytes] - size of packet including the 4 byte packet type
[size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
[4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
[structure]
[structure] - one member contains length of trailing data (expressed in packet-dependant structures)
[trailing data] - optional variable-length data that only some packets make use of
*/
// error checking calloc wrapper
@@ -71,6 +71,42 @@ inline void* xmalloc(size_t sz) {
return res;
}
// overflow-safe validation of variable-length packets
// for outbound packets
inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
// check for multiplication overflow
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
return false;
// it's safe to multiply
size_t trailing = npayloads * plsize;
// does it fit in a packet?
if (base + trailing > CN_PACKET_BUFFER_SIZE)
return false;
// everything is a-ok!
return true;
}
// for inbound packets
inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
// check for multiplication overflow
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
return false;
// it's safe to multiply
size_t trailing = npayloads * plsize;
// make sure size is exact
// datasize has already been validated against CN_PACKET_BUFFER_SIZE
if (datasize != base + trailing)
return false;
// everything is a-ok!
return true;
}
namespace CNSocketEncryption {
// you won't believe how complicated they made it in the client :facepalm:
static constexpr const char* defaultKey = "m@rQn~W#";
@@ -104,7 +140,7 @@ private:
uint64_t EKey;
uint64_t FEKey;
int32_t readSize = 0;
uint8_t readBuffer[MAX_PACKETSIZE];
uint8_t readBuffer[CN_PACKET_BUFFER_SIZE];
int readBufferIndex = 0;
bool activelyReading = false;
bool alive = true;

131
src/CombatManager.cpp Normal file
View File

@@ -0,0 +1,131 @@
#include "CombatManager.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "ItemManager.hpp"
#include <assert.h>
void CombatManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_BEGIN, combatBegin);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_END, combatEnd);
REGISTER_SHARD_PACKET(P_CL2FE_DOT_DAMAGE_ONOFF, dotDamageOnOff);
}
void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
// sanity check
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs), pkt->iNPCCnt, sizeof(int32_t), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n";
return;
}
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs));
/*
* Due to the possibility of multiplication overflow (and regular buffer overflow),
* both incoming and outgoing variable-length packets must be validated, at least if
* the number of trailing structs isn't well known (ie. it's from the client).
*/
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) {
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_NPCs_SUCC packet size\n";
return;
}
// initialize response struct
size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult);
uint8_t respbuf[4096];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_ATTACK_NPCs_SUCC *resp = (sP_FE2CL_PC_ATTACK_NPCs_SUCC*)respbuf;
sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC));
resp->iNPCCnt = pkt->iNPCCnt;
for (int i = 0; i < pkt->iNPCCnt; i++) {
if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) {
// not sure how to best handle this
std::cout << "[WARN] pcAttackNpcs: mob ID not found" << std::endl;
return;
}
BaseNPC& mob = NPCManager::NPCs[pktdata[i]];
mob.appearanceData.iHP -= 100;
if (mob.appearanceData.iHP <= 0)
giveReward(sock);
// TODO: despawn mobs when they die
respdata[i].iID = mob.appearanceData.iNPC_ID;
respdata[i].iDamage = 100;
respdata[i].iHP = mob.appearanceData.iHP;
respdata[i].iHitFlag = 2;
}
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen);
// a bit of a hack: these are the same size, so we can reuse the output packet
assert(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_NPCs));
sP_FE2CL_PC_ATTACK_NPCs *resp1 = (sP_FE2CL_PC_ATTACK_NPCs*)respbuf;
resp1->iPC_ID = plr->iID;
// send to other players
for (CNSocket *s : PlayerManager::players[sock].viewable) {
if (s == sock)
continue;
s->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen);
}
}
void CombatManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::giveReward(CNSocket *sock) {
Player *plr = PlayerManager::getPlayer(sock);
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BUFFER_SIZE);
// we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer!
memset(respbuf, 0, resplen);
// update player
plr->money += 50;
plr->fusionmatter += 70;
// simple rewards
reward->m_iCandy = plr->money;
reward->m_iFusionMatter = plr->fusionmatter;
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->iItemCnt = 1; // remember to update resplen if you change this
int slot = ItemManager::findFreeSlot(plr);
if (slot == -1) {
// no room for an item, but you still get FM and taros
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM));
} else {
// item reward
item->sItem.iType = 9;
item->sItem.iID = 1;
item->iSlotNum = slot;
item->eIL = 1; // Inventory Location. 1 means player inventory.
// update player
plr->Inven[slot] = item->sItem;
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
}
}

16
src/CombatManager.hpp Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "CNProtocol.hpp"
#include "CNShared.hpp"
#include "CNShardServer.hpp"
namespace CombatManager {
void init();
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
void combatBegin(CNSocket *sock, CNPacketData *data);
void combatEnd(CNSocket *sock, CNPacketData *data);
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void giveReward(CNSocket *sock);
}

View File

@@ -5,7 +5,7 @@
#define STRINGIFY(x) PacketMap(x, #x)
/*
* Turns out there's not better way to do this...
* Turns out there isn't better way to do this...
* We'll only support CL2* packets for now, since we only
* need to print those.
*/

View File

@@ -4,6 +4,9 @@
#include "PlayerManager.hpp"
#include "Player.hpp"
#include <string.h> // for memset() and memcmp()
#include <assert.h>
void ItemManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);
@@ -18,6 +21,7 @@ void ItemManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER, itemTradeUnregisterItemHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CASH_REGISTER, itemTradeRegisterCashHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_EMOTES_CHAT, itemTradeChatHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_CHEST_OPEN, chestOpenHandler);
}
void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
@@ -664,4 +668,64 @@ void ItemManager::itemTradeChatHandler(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT));
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT));
}
}
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 *pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
// item giving packet
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BUFFER_SIZE);
// we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer!
memset(respbuf, 0, resplen);
// simple rewards
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->iItemCnt = 1; // remember to update resplen if you change this
// item reward
item->sItem.iType = 0;
item->sItem.iID = 96;
item->iSlotNum = pkt->iSlotNum;
item->eIL = pkt->eIL;
// update player
plr->Inven[pkt->iSlotNum] = item->sItem;
// transmit item
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
// chest opening acknowledgement packet
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
resp.iSlotNum = pkt->iSlotNum;
std::cout << "opening chest..." << std::endl;
sock->sendPacket((void*)&resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, sizeof(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC));
}
// TODO: use this in cleaned up ItemManager
int ItemManager::findFreeSlot(Player *plr) {
int i;
sItemBase free;
memset((void*)&free, 0, sizeof(sItemBase));
for (i = 0; i < AINVEN_COUNT; i++)
if (memcmp((void*)&plr->Inven[i], (void*)&free, sizeof(sItemBase)) == 0)
return i;
// not found
return -1;
}

View File

@@ -1,9 +1,11 @@
#pragma once
#include "CNShardServer.hpp"
#include "Player.hpp"
namespace ItemManager {
void init();
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
void itemGMGiveHandler(CNSocket* sock, CNPacketData* data);
@@ -17,4 +19,7 @@ namespace ItemManager {
void itemTradeUnregisterItemHandler(CNSocket* sock, CNPacketData* data);
void itemTradeRegisterCashHandler(CNSocket* sock, CNPacketData* data);
void itemTradeChatHandler(CNSocket* sock, CNPacketData* data);
void chestOpenHandler(CNSocket* sock, CNPacketData* data);
int findFreeSlot(Player *plr);
}

View File

@@ -37,7 +37,7 @@ void NPCManager::init() {
// load temporary mob dump
try {
std::ifstream inFile("mobs.json"); // not in settings, since it's temp
std::ifstream inFile("data/mobs.json"); // not in settings, since it's temp
nlohmann::json npcData;
// read file into json

View File

@@ -15,6 +15,7 @@ struct Player {
int HP;
int slot; // player slot, not nano slot
int32_t money;
int32_t fusionmatter;
sPCStyle PCStyle;
sPCStyle2 PCStyle2;
sNano Nanos[37]; // acquired nanos

View File

@@ -32,6 +32,7 @@ void PlayerManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setSpecialSwitchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_ON, PlayerManager::enterPlayerVehicle);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, PlayerManager::exitPlayerVehicle);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, PlayerManager::changePlayerGuide);
}
void PlayerManager::addPlayer(CNSocket* key, Player plr) {
@@ -651,6 +652,21 @@ void PlayerManager::setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
}
void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_CHANGE_MENTOR))
return;
sP_CL2FE_REQ_PC_CHANGE_MENTOR *pkt = (sP_CL2FE_REQ_PC_CHANGE_MENTOR*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, resp);
Player *plr = getPlayer(sock);
resp.iMentor = pkt->iMentor;
resp.iMentorCnt = 1;
resp.iFusionMatter = plr->fusionmatter; // no cost
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC));
}
#pragma region Helper methods
Player *PlayerManager::getPlayer(CNSocket* key) {
return players[key].plr;

View File

@@ -45,6 +45,7 @@ namespace PlayerManager {
void exitGame(CNSocket* sock, CNPacketData* data);
void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
void changePlayerGuide(CNSocket *sock, CNPacketData *data);
void enterPlayerVehicle(CNSocket* sock, CNPacketData* data);
void exitPlayerVehicle(CNSocket* sock, CNPacketData* data);

View File

@@ -2,6 +2,7 @@
#include "CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "ChatManager.hpp"
#include "CombatManager.hpp"
#include "ItemManager.hpp"
#include "MissionManager.hpp"
#include "NanoManager.hpp"
@@ -17,10 +18,21 @@
#endif
#include <string>
CNShardServer *shardServer;
std::thread *shardThread;
void startShard(CNShardServer* server) {
server->start();
}
// terminate gracefully on SIGINT (for gprof)
void terminate(int arg) {
std::cout << "OpenFusion: terminating" << std::endl;
shardServer->kill();
shardThread->join();
exit(0);
}
int main() {
#ifdef _WIN32
WSADATA wsaData;
@@ -31,12 +43,14 @@ int main() {
#else
// tell the OS to not kill us if you use a broken pipe, just let us know thru recv() or send()
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, terminate); // TODO: use sigaction() instead
#endif
settings::init();
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
PlayerManager::init();
ChatManager::init();
CombatManager::init();
ItemManager::init();
MissionManager::init();
NanoManager::init();
@@ -46,14 +60,14 @@ int main() {
std::cout << "[INFO] Starting Server Threads..." << std::endl;
CNLoginServer loginServer(settings::LOGINPORT);
CNShardServer shardServer(settings::SHARDPORT);
shardServer = new CNShardServer(settings::SHARDPORT);
std::thread shardThread(startShard, (CNShardServer*)&shardServer);
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
loginServer.start();
shardServer.kill();
shardThread.join();
shardServer->kill();
shardThread->join();
#ifdef _WIN32
WSACleanup();

View File

@@ -18,8 +18,8 @@ int settings::SPAWN_X = 179213;
int settings::SPAWN_Y = 268451;
int settings::SPAWN_Z = -4210;
std::string settings::GMPASS = "pass";
std::string settings::NPCJSON = "NPCs.json";
std::string settings::WARPJSON = "warps.json";
std::string settings::NPCJSON = "data/NPCs.json";
std::string settings::WARPJSON = "data/warps.json";
std::string settings::MOTDSTRING = "Welcome to OpenFusion!";
bool settings::GM = false;