From 9e9161083d8164def99efc0a3faed210a8b60348 Mon Sep 17 00:00:00 2001 From: dongresource Date: Thu, 27 Aug 2020 21:35:45 +0200 Subject: [PATCH 01/10] Reword some comments and correct paths in the Readme. --- .vimrc | 6 +++--- README.md | 14 +++++++------- src/Defines.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.vimrc b/.vimrc index c5c65b2..32e2a85 100644 --- a/.vimrc +++ b/.vimrc @@ -1,9 +1,9 @@ " vim configuration file -" you will need to put 'set exrc' and 'set secure' into your main .vimrc file, +" You will need to put 'set exrc' and 'set secure' into your main .vimrc file, " in which case this file will be loaded automatically, but *only* if you -" start vim in this dir, or you can just load it directly with ':so .vimrc' -" every time. +" start vim in this dir. Alternatively you can just load it directly with +" ':so .vimrc' every time. set tabstop=4 set shiftwidth=4 set expandtab diff --git a/README.md b/README.md index aa23791..7b7dc10 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ tl;dr: From then on, any time you want to run the "game": -3. Run `OpenFusionRelease/winfusion.exe` +3. Run `Server/winfusion.exe` 4. Run `FreeClient/OpenFusionClient.exe` Currently the client by default connects to a public server hosted by Cake. Change the loginInfo.php to point to your own server if you want to host your own. @@ -61,7 +61,7 @@ When the player clicks "ENTER THE GAME" (or completes the tutorial), the login s ## Configuration -You can change the ports the FusionFall server listens on in `OpenFusion/config.ini`. Make sure the login server port is in sync with `loginInfo.php`. +You can change the ports the FusionFall server listens on in `Server/config.ini`. Make sure the login server port is in sync with `loginInfo.php`. The shard port needs no such synchronization. You can also configure the distance at which you'll be able to see other players, though by default it's already as high as you'll want it. @@ -103,11 +103,11 @@ To make your landwalking experience more pleasant, you can make use of a few adm * `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.). ### Item commands -* /itemN [type] [itemId] [amount] +* `/itemN [type] [itemId] [amount]` (Refer to the [item list](https://docs.google.com/spreadsheets/d/1mpoJ9iTHl_xLI4wQ_9UvIDYNcsDYscdkyaGizs43TCg/)) ### Nano commands -* /nano [id] (1-36) -* /nano_equip [id] (1-36) [slot] (0-2) -* /nano_unequip [slot] (0-2) -* /nano_active [slot] (0-2) +* `/nano [id] (1-36)` +* `/nano_equip [id] (1-36) [slot] (0-2)` +* `/nano_unequip [slot] (0-2)` +* `/nano_active [slot] (0-2)` diff --git a/src/Defines.cpp b/src/Defines.cpp index e0ce9ce..8552a43 100644 --- a/src/Defines.cpp +++ b/src/Defines.cpp @@ -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. */ From 3c43dd0193cd444cd3dc90e4e8513653e003375d Mon Sep 17 00:00:00 2001 From: dongresource Date: Thu, 27 Aug 2020 21:39:04 +0200 Subject: [PATCH 02/10] Try to transmit FF packets in one go, instead of sending the id first. --- src/CNProtocol.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/CNProtocol.cpp b/src/CNProtocol.cpp index 1c8c1a1..097d333 100644 --- a/src/CNProtocol.cpp +++ b/src/CNProtocol.cpp @@ -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) { From c8c2f4b05f0da51d07b8c2a91d947cb43632fb61 Mon Sep 17 00:00:00 2001 From: dongresource Date: Thu, 27 Aug 2020 21:42:04 +0200 Subject: [PATCH 03/10] Catch SIGINT with signal(), to allow for gprof instrumentation. Note: signal() is undefined behaviour in multithreaded programs and is unportable for handling signals in general. This will need to be replaced with sigaction() or something. --- src/main.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cd4f46d..ef79250 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,10 +17,21 @@ #endif #include +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,6 +42,7 @@ 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; @@ -46,14 +58,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(); From 64accecc30c7b98f097c680c40ad6b3fc85b5fe0 Mon Sep 17 00:00:00 2001 From: dongresource Date: Thu, 27 Aug 2020 22:16:52 +0200 Subject: [PATCH 04/10] Initial implementation of CombatManager. Overflow detection must still be implemented. --- Makefile | 2 ++ src/CombatManager.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++ src/CombatManager.hpp | 16 +++++++++ src/Player.hpp | 1 + src/main.cpp | 2 ++ 5 files changed, 105 insertions(+) create mode 100644 src/CombatManager.cpp create mode 100644 src/CombatManager.hpp diff --git a/Makefile b/Makefile index 97e18b6..d32e769 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ CXX_SRC=\ src/contrib/sqlite/sqlite3pp.cpp\ src/contrib/sqlite/sqlite3ppext.cpp\ src/ChatManager.cpp\ + src/CombatManager.cpp\ src/CNLoginServer.cpp\ src/CNProtocol.cpp\ src/CNShardServer.cpp\ @@ -63,6 +64,7 @@ CXX_HDR=\ src/contrib/INIReader.hpp\ src/contrib/JSON.hpp\ src/ChatManager.hpp\ + src/CombatManager.hpp\ src/CNLoginServer.hpp\ src/CNProtocol.hpp\ src/CNShardServer.hpp\ diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp new file mode 100644 index 0000000..73fde1d --- /dev/null +++ b/src/CombatManager.cpp @@ -0,0 +1,84 @@ +#include "CombatManager.hpp" +#include "PlayerManager.hpp" +#include "NPCManager.hpp" + +#include + +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) { + // generic malformed packet checks are not applicable to variable-length packets + sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf; + + if (data->size != sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs) + pkt->iNPCCnt * 4) { + std::cout << "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)); + + std::printf("iNPCCnt: %d\n", pkt->iNPCCnt); + + // initialize response struct + // IMPORTANT TODO: verify that resplen doesn't overflow!!! + size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult); + uint8_t *respbuf = (uint8_t*)xmalloc(resplen); + 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++) { + std::cout << pktdata[i] << std::endl; + + 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; + std::cout << "mob health is now " << mob.appearanceData.iHP << std::endl; + + if (mob.appearanceData.iHP <= 0) + giveReward(sock); + + respdata[i].iID = mob.appearanceData.iNPC_ID; + respdata[i].iDamage = 100; + respdata[i].iHP = mob.appearanceData.iHP; + respdata[i].iHitFlag = 2; + } + + std::cout << "sending packet of length " << resplen << std::endl; + sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen); + + free(respbuf); +} + +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) { + // reward testing + INITSTRUCT(sP_FE2CL_REP_REWARD_ITEM, reward); + Player *plr = PlayerManager::getPlayer(sock); + + // update player + plr->money += 50; + plr->fusionmatter += 70; + + reward.m_iCandy = plr->money; + reward.m_iFusionMatter = plr->fusionmatter; + reward.iFatigue = 100; // prevents warning message + reward.iFatigue_Level = 1; + + sock->sendPacket((void*)&reward, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM)); +} diff --git a/src/CombatManager.hpp b/src/CombatManager.hpp new file mode 100644 index 0000000..5d321ac --- /dev/null +++ b/src/CombatManager.hpp @@ -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); +} diff --git a/src/Player.hpp b/src/Player.hpp index e60bdf8..3d780d1 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -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 diff --git a/src/main.cpp b/src/main.cpp index ef79250..1a1990b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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" @@ -49,6 +50,7 @@ int main() { std::cout << "[INFO] Intializing Packet Managers..." << std::endl; PlayerManager::init(); ChatManager::init(); + CombatManager::init(); ItemManager::init(); MissionManager::init(); NanoManager::init(); From 67d899efe63d0a4118cd4311dfde258143e454cc Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 16:09:26 +0200 Subject: [PATCH 05/10] Implemented proper validation of variable-length packets. Also changed output buffer in pcAttackNpcs() from dynamically to statically allocated. This in itself is temporary as I have a better idea as to how we can allocate buffers with a bit less boilerplate. --- src/CNProtocol.cpp | 2 +- src/CNProtocol.hpp | 42 +++++++++++++++++++++++++++++++++++++++--- src/CombatManager.cpp | 25 +++++++++++++++---------- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/CNProtocol.cpp b/src/CNProtocol.cpp index 097d333..fd568b8 100644 --- a/src/CNProtocol.cpp +++ b/src/CNProtocol.cpp @@ -170,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; } diff --git a/src/CNProtocol.hpp b/src/CNProtocol.hpp index d2f9843..b24959b 100644 --- a/src/CNProtocol.hpp +++ b/src/CNProtocol.hpp @@ -1,6 +1,5 @@ #pragma once -#define MAX_PACKETSIZE 8192 #define DEBUGLOG(x) if (settings::VERBOSITY) {x}; #include @@ -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; diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp index 73fde1d..845f879 100644 --- a/src/CombatManager.cpp +++ b/src/CombatManager.cpp @@ -13,22 +13,31 @@ void CombatManager::init() { } void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { - // generic malformed packet checks are not applicable to variable-length packets sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf; - if (data->size != sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs) + pkt->iNPCCnt * 4) { - std::cout << "bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n"; + // 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)); - std::printf("iNPCCnt: %d\n", pkt->iNPCCnt); + /* + * Due to the possibility of multiplication overflow (and regular buffer overflow), + * both incoming and outgoing variable-length packets must be validated. + */ + if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) { + std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n"; + return; + } // initialize response struct - // IMPORTANT TODO: verify that resplen doesn't overflow!!! size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult); - uint8_t *respbuf = (uint8_t*)xmalloc(resplen); + 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)); @@ -45,7 +54,6 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { BaseNPC& mob = NPCManager::NPCs[pktdata[i]]; mob.appearanceData.iHP -= 100; - std::cout << "mob health is now " << mob.appearanceData.iHP << std::endl; if (mob.appearanceData.iHP <= 0) giveReward(sock); @@ -56,10 +64,7 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { respdata[i].iHitFlag = 2; } - std::cout << "sending packet of length " << resplen << std::endl; sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen); - - free(respbuf); } void CombatManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub From 4df812f996e97858e16cd3720ea827b175d0dcc0 Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 18:25:03 +0200 Subject: [PATCH 06/10] Implemented crates (dropping and opening). Also fixed a bug in vaildOutVarPacket(). --- src/CNProtocol.hpp | 2 +- src/CombatManager.cpp | 47 ++++++++++++++++++++++-------- src/ItemManager.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++- src/ItemManager.hpp | 5 ++++ 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/src/CNProtocol.hpp b/src/CNProtocol.hpp index b24959b..cc47ece 100644 --- a/src/CNProtocol.hpp +++ b/src/CNProtocol.hpp @@ -82,7 +82,7 @@ inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) { size_t trailing = npayloads * plsize; // does it fit in a packet? - if (base + trailing <= CN_PACKET_BUFFER_SIZE) + if (base + trailing > CN_PACKET_BUFFER_SIZE) return false; // everything is a-ok! diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp index 845f879..16bbeb3 100644 --- a/src/CombatManager.cpp +++ b/src/CombatManager.cpp @@ -1,8 +1,9 @@ #include "CombatManager.hpp" #include "PlayerManager.hpp" #include "NPCManager.hpp" +#include "ItemManager.hpp" -#include +#include void CombatManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); @@ -28,7 +29,7 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { * both incoming and outgoing variable-length packets must be validated. */ if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) { - std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n"; + std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_NPCs_SUCC packet size\n"; return; } @@ -44,8 +45,6 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { resp->iNPCCnt = pkt->iNPCCnt; for (int i = 0; i < pkt->iNPCCnt; i++) { - std::cout << pktdata[i] << std::endl; - 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; @@ -72,18 +71,44 @@ void CombatManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub void CombatManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub void CombatManager::giveReward(CNSocket *sock) { - // reward testing - INITSTRUCT(sP_FE2CL_REP_REWARD_ITEM, reward); 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; - reward.m_iCandy = plr->money; - reward.m_iFusionMatter = plr->fusionmatter; - reward.iFatigue = 100; // prevents warning message - reward.iFatigue_Level = 1; + // 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 - sock->sendPacket((void*)&reward, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM)); + 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); + } } diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index ad11e55..d43817a 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -4,6 +4,9 @@ #include "PlayerManager.hpp" #include "Player.hpp" +#include // for memset() and memcmp() +#include + 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)); -} \ No newline at end of file +} + +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; +} diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 44cf281..4531095 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -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); } From 3b35e0017a86ff51ac235ff2b9c76c4ab5cdc8b3 Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 18:31:53 +0200 Subject: [PATCH 07/10] Moved all JSON files into a dedicated data directory. --- config.ini | 4 ++-- NPCs.json => data/NPCs.json | 0 mobs.json => data/mobs.json | 0 warps.json => data/warps.json | 0 src/NPCManager.cpp | 2 +- src/settings.cpp | 4 ++-- 6 files changed, 5 insertions(+), 5 deletions(-) rename NPCs.json => data/NPCs.json (100%) rename mobs.json => data/mobs.json (100%) rename warps.json => data/warps.json (100%) diff --git a/config.ini b/config.ini index cdb6bcf..56e7550 100644 --- a/config.ini +++ b/config.ini @@ -23,9 +23,9 @@ npcdistance=16000 # little message players see when they enter the game motd=Welcome to OpenFusion! # NPC json data -npcdata=NPCs.json +npcdata=data/NPCs.json # warp target json data -warpdata=warps.json +warpdata=data/warps.json # is everyone a GM? gm=true diff --git a/NPCs.json b/data/NPCs.json similarity index 100% rename from NPCs.json rename to data/NPCs.json diff --git a/mobs.json b/data/mobs.json similarity index 100% rename from mobs.json rename to data/mobs.json diff --git a/warps.json b/data/warps.json similarity index 100% rename from warps.json rename to data/warps.json diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 9988f1d..1e34704 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -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 diff --git a/src/settings.cpp b/src/settings.cpp index 976836e..07d0bc4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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; From 72a811d6ab2507c9a74ee438205cfaaa806bf9d1 Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 21:42:00 +0200 Subject: [PATCH 08/10] Implemented guide changing. This means the Time Machine works as well. --- src/CombatManager.cpp | 4 +++- src/PlayerManager.cpp | 16 ++++++++++++++++ src/PlayerManager.hpp | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp index 16bbeb3..2836dab 100644 --- a/src/CombatManager.cpp +++ b/src/CombatManager.cpp @@ -26,7 +26,8 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { /* * Due to the possibility of multiplication overflow (and regular buffer overflow), - * both incoming and outgoing variable-length packets must be validated. + * 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"; @@ -56,6 +57,7 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { if (mob.appearanceData.iHP <= 0) giveReward(sock); + // TODO: despawn mobs when they die respdata[i].iID = mob.appearanceData.iNPC_ID; respdata[i].iDamage = 100; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 2b1ad85..7f87515 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -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; diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 78f904b..52eb95a 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -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); From a067975f27af5d2469cc0f7d5d03062b7dd5db6a Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 22:01:49 +0200 Subject: [PATCH 09/10] Players can now see eachother fight monsters. --- src/CombatManager.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp index 2836dab..bb77b75 100644 --- a/src/CombatManager.cpp +++ b/src/CombatManager.cpp @@ -15,6 +15,7 @@ void CombatManager::init() { 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)) { @@ -66,6 +67,20 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { } 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 From 322bc466047c5418c537affceb5b81c57d710c20 Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 28 Aug 2020 22:44:29 +0200 Subject: [PATCH 10/10] Support plain POSIX make. Also standardized the new variable names. --- Makefile | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index d32e769..0c8b5a5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC=clang CXX=clang++ # -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!) -CCFLAGS=-O3 #-g3 -fsanitize=address +CFLAGS=-O3 #-g3 -fsanitize=address CXXFLAGS=-Wall -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address LDFLAGS=-lpthread -ldl # specifies the name of our exectuable @@ -14,19 +14,19 @@ PROTOCOL_VERSION?=104 # Windows-specific WIN_CC=x86_64-w64-mingw32-gcc WIN_CXX=x86_64-w64-mingw32-g++ -WIN_CCFLAGS=-O3 #-g3 -fsanitize=address +WIN_CFLAGS=-O3 #-g3 -fsanitize=address WIN_CXXFLAGS=-Wall -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address WIN_LDFLAGS=-static -lws2_32 -lwsock32 WIN_SERVER=bin/winfusion.exe -CC_SRC=\ +CSRC=\ src/contrib/bcrypt/bcrypt.c\ src/contrib/bcrypt/crypt_blowfish.c\ src/contrib/bcrypt/crypt_gensalt.c\ src/contrib/bcrypt/wrapper.c\ src/contrib/sqlite/sqlite3.c\ -CXX_SRC=\ +CXXSRC=\ src/contrib/sqlite/sqlite3pp.cpp\ src/contrib/sqlite/sqlite3ppext.cpp\ src/ChatManager.cpp\ @@ -48,7 +48,7 @@ CXX_SRC=\ src/settings.cpp\ # headers (for timestamp purposes) -CC_HDR=\ +CHDR=\ src/contrib/bcrypt/bcrypt.h\ src/contrib/bcrypt/crypt_blowfish.h\ src/contrib/bcrypt/crypt_gensalt.h\ @@ -57,7 +57,7 @@ CC_HDR=\ src/contrib/sqlite/sqlite3.h\ src/contrib/sqlite/sqlite3ext.h\ -CXX_HDR=\ +CXXHDR=\ src/contrib/bcrypt/BCrypt.hpp\ src/contrib/sqlite/sqlite3pp.h\ src/contrib/sqlite/sqlite3ppext.h\ @@ -82,10 +82,10 @@ CXX_HDR=\ src/PlayerManager.hpp\ src/settings.hpp\ -CC_OBJ=$(CC_SRC:.c=.o) -CXX_OBJ=$(CXX_SRC:.cpp=.o) +COBJ=$(CSRC:.c=.o) +CXXOBJ=$(CXXSRC:.cpp=.o) -OBJ=$(CC_OBJ) $(CXX_OBJ) +OBJ=$(COBJ) $(CXXOBJ) all: $(SERVER) @@ -94,22 +94,24 @@ windows: $(SERVER) # assign Windows-specific values if targeting Windows windows : CC=$(WIN_CC) windows : CXX=$(WIN_CXX) -windows : CCFLAGS=$(WIN_CCFLAGS) +windows : CFLAGS=$(WIN_CFLAGS) windows : CXXFLAGS=$(WIN_CXXFLAGS) windows : LDFLAGS=$(WIN_LDFLAGS) windows : SERVER=$(WIN_SERVER) -$(CC_OBJ): %.o: %.c $(CC_HDR) - $(CC) -c $(CCFLAGS) -o $@ $< +.SUFFIX: .o .c .cpp .hpp -$(CXX_OBJ): %.o: %.cpp $(CXX_HDR) +.c.o: $(CHDR) + $(CC) -c $(CFLAGS) -o $@ $< + +.cpp.o: $(CXXHDR) $(CXX) -c $(CXXFLAGS) -o $@ $< -$(SERVER): $(OBJ) $(CC_HDR) $(CXX_HDR) +$(SERVER): $(OBJ) $(CHDR) $(CXXHDR) mkdir -p bin $(CXX) $(OBJ) $(LDFLAGS) -o $(SERVER) .PHONY: all windows clean clean: - rm -f $(OBJ) $(SERVER) $(WIN_SERVER) \ No newline at end of file + rm -f $(OBJ) $(SERVER) $(WIN_SERVER)