diff --git a/src/Player.hpp b/src/Player.hpp index c486012..baa77b1 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -14,9 +14,7 @@ struct Player : public Entity { int accountId = 0; int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) - int64_t SerialKey = 0; int32_t iID = 0; - uint64_t FEKey = 0; int level = 0; int HP = 0; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index ff6ce02..e3cb3fd 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -28,7 +28,7 @@ using namespace PlayerManager; std::map PlayerManager::players; -static void addPlayer(CNSocket* key, Player plr) { +static void addPlayer(CNSocket* key, Player& plr) { Player *p = new Player(); // copy object into heap memory @@ -209,20 +209,22 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { auto enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response); - // TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL - Player plr = CNShared::getPlayer(enter->iEnterSerialKey); + LoginMetadata *lm = CNShared::getLoginMetadata(enter->iEnterSerialKey); + if (lm == nullptr) { + delete lm; + + std::cout << "[WARN] Refusing invalid REQ_PC_ENTER" << std::endl; + INITSTRUCT(sP_FE2CL_REP_PC_ENTER_FAIL, fail); + sock->sendPacket(fail, P_FE2CL_REP_PC_ENTER_FAIL); + return; + } + + // for convenience + Player& plr = lm->plr; plr.groupCnt = 1; plr.iIDGroup = plr.groupIDs[0] = plr.iID; - DEBUGLOG( - std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl; - std::cout << "\tID: " << AUTOU16TOU8(enter->szID) << std::endl; - std::cout << "\tSerial: " << enter->iEnterSerialKey << std::endl; - std::cout << "\tTemp: " << enter->iTempValue << std::endl; - std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl; - ) - // check if account is already in use if (isAccountInUse(plr.accountId)) { // kick the other player @@ -267,7 +269,6 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { // nanos for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) { response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i]; - //response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i] = {0}; } for (int i = 0; i < 3; i++) { response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i]; @@ -308,11 +309,10 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1]; } - plr.SerialKey = enter->iEnterSerialKey; plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1)); - sock->setFEKey(plr.FEKey); + sock->setFEKey(lm->FEKey); sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); @@ -320,7 +320,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { // transmit MOTD after entering the game, so the client hopefully changes modes on time Chat::sendServerMessage(sock, settings::MOTDSTRING); + // copy Player object into the shard addPlayer(sock, plr); + // check if there is an expiring vehicle Items::checkItemExpire(sock, getPlayer(sock)); @@ -337,6 +339,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { for (auto& pair : players) if (pair.second->notify) Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined."); + + // deallocate lm (and therefore the plr object) + delete lm; } void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { @@ -705,4 +710,6 @@ void PlayerManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, exitPlayerVehicle); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, changePlayerGuide); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET, setFirstUseFlag); + + REGISTER_SHARD_TIMER(CNShared::pruneLoginMetadata, CNSHARED_TIMEOUT); } diff --git a/src/core/CNShared.cpp b/src/core/CNShared.cpp index 92469a6..5ca6574 100644 --- a/src/core/CNShared.cpp +++ b/src/core/CNShared.cpp @@ -5,23 +5,47 @@ #else #include #endif -std::map CNShared::players; -std::mutex playerCrit; -void CNShared::setPlayer(int64_t sk, Player& plr) { - std::lock_guard lock(playerCrit); // the lock will be removed when the function ends +static std::unordered_map login; +static std::mutex mtx; - players[sk] = plr; +void CNShared::storeLoginMetadata(int64_t sk, LoginMetadata *lm) { + std::lock_guard lock(mtx); + + // take ownership of connection data + login[sk] = lm; } -Player CNShared::getPlayer(int64_t sk) { - std::lock_guard lock(playerCrit); // the lock will be removed when the function ends +LoginMetadata* CNShared::getLoginMetadata(int64_t sk) { + std::lock_guard lock(mtx); - return players[sk]; + // fail if the key isn't found + if (login.find(sk) == login.end()) + return nullptr; + + // transfer ownership of connection data to shard + LoginMetadata *lm = login[sk]; + login.erase(sk); + + return lm; } -void CNShared::erasePlayer(int64_t sk) { - std::lock_guard lock(playerCrit); // the lock will be removed when the function ends +void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) { + std::lock_guard lock(mtx); - players.erase(sk); + auto it = login.begin(); + while (it != login.end()) { + auto& sk = it->first; + auto& lm = it->second; + + if (lm->timestamp + CNSHARED_TIMEOUT > currTime) { + std::cout << "[WARN] Pruning hung connection attempt" << std::endl; + + // deallocate object and remove map entry + delete login[sk]; + it = login.erase(it); // skip the invalidated iterator + } else { + it++; + } + } } diff --git a/src/core/CNShared.hpp b/src/core/CNShared.hpp index c0ddd4e..e99e07b 100644 --- a/src/core/CNShared.hpp +++ b/src/core/CNShared.hpp @@ -10,11 +10,16 @@ #include "Player.hpp" -namespace CNShared { - // serialkey corresponds to player data - extern std::map players; +#define CNSHARED_TIMEOUT 30000 - void setPlayer(int64_t sk, Player& plr); - Player getPlayer(int64_t sk); - void erasePlayer(int64_t sk); +struct LoginMetadata { + uint64_t FEKey; + Player plr; + time_t timestamp; +}; + +namespace CNShared { + void storeLoginMetadata(int64_t sk, LoginMetadata *lm); + LoginMetadata* getLoginMetadata(int64_t sk); + void pruneLoginMetadata(CNServer *serv, time_t currTime); } diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index b87ef2b..c8c2087 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -468,21 +468,24 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) { resp.g_FE_ServerIP[strlen(shard_ip)] = '\0'; resp.g_FE_ServerPort = settings::SHARDPORT; - // pass player to CNShared - Player passPlayer = {}; - Database::getPlayer(&passPlayer, selection->iPC_UID); + LoginMetadata *lm = new LoginMetadata(); + lm->FEKey = sock->getFEKey(); + lm->timestamp = getTime(); + + Database::getPlayer(&lm->plr, selection->iPC_UID); // this should never happen but for extra safety - if (passPlayer.iID == 0) + if (lm->plr.iID == 0) return invalidCharacter(sock); - passPlayer.FEKey = sock->getFEKey(); - resp.iEnterSerialKey = passPlayer.iID; - CNShared::setPlayer(resp.iEnterSerialKey, passPlayer); + resp.iEnterSerialKey = Rand::rand(); // TODO: cryptographic RNG + + // transfer ownership of connection data to CNShared + CNShared::storeLoginMetadata(resp.iEnterSerialKey, lm); sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC); // update current slot in DB - Database::updateSelected(loginSessions[sock].userID, passPlayer.slot); + Database::updateSelected(loginSessions[sock].userID, lm->plr.slot); } void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) { diff --git a/src/servers/CNShardServer.cpp b/src/servers/CNShardServer.cpp index 0d5c046..036df15 100644 --- a/src/servers/CNShardServer.cpp +++ b/src/servers/CNShardServer.cpp @@ -99,14 +99,7 @@ void CNShardServer::_killConnection(CNSocket* cns) { if (PlayerManager::players.find(cns) == PlayerManager::players.end()) return; - Player* plr = PlayerManager::getPlayer(cns); - - int64_t key = plr->SerialKey; - PlayerManager::removePlayer(cns); // removes the player from the list and saves it to DB - - // remove from CNShared - CNShared::erasePlayer(key); } void CNShardServer::killConnection(CNSocket *cns) {