mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-09-30 11:40:06 +00:00
[refactor] Move files to core/ and servers/ subdirectories
CNProtocol, CNShared, CNStructs and Defines are now in core/. CNLoginServer, CNShardServer and Monitor are now in servers/. core/Core.hpp wraps all the core headers except for CNShared.hpp. Defines.cpp has been renamed to Packets.cpp, and so has its corresponding namespace, but not the header file. This is in preparation for upcoming changes.
This commit is contained in:
649
src/servers/CNLoginServer.cpp
Normal file
649
src/servers/CNLoginServer.cpp
Normal file
@@ -0,0 +1,649 @@
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
#include "core/CNShared.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
#include <regex>
|
||||
#include "bcrypt/BCrypt.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||
|
||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||
port = p;
|
||||
pHandler = &CNLoginServer::handlePacket;
|
||||
init();
|
||||
}
|
||||
|
||||
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
printPacket(data, CL2LS);
|
||||
|
||||
switch (data->type) {
|
||||
case P_CL2LS_REQ_LOGIN: {
|
||||
login(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REP_LIVE_CHECK: {
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHECK_CHAR_NAME: {
|
||||
nameCheck(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_SAVE_CHAR_NAME: {
|
||||
nameSave(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_CREATE: {
|
||||
characterCreate(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_DELETE: {
|
||||
characterDelete(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHAR_SELECT: {
|
||||
characterSelect(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_SAVE_CHAR_TUTOR: {
|
||||
finishTutorial(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_CHANGE_CHAR_NAME: {
|
||||
changeName(sock, data);
|
||||
break;
|
||||
}
|
||||
case P_CL2LS_REQ_PC_EXIT_DUPLICATE:{
|
||||
duplicateExit(sock, data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (settings::VERBOSITY)
|
||||
std::cerr << "OpenFusion: LOGIN UNIMPLM ERR. PacketType: " << Packets::p2str(CL2LS, data->type) << " (" << data->type << ")" << std::endl;
|
||||
break;
|
||||
/*
|
||||
* Unimplemented CL2LS packets:
|
||||
* P_CL2LS_REQ_SHARD_SELECT - we skip it in char select
|
||||
* P_CL2LS_CHECK_NAME_LIST - unused by the client
|
||||
* P_CL2LS_REQ_SERVER_SELECT
|
||||
* P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region packets
|
||||
|
||||
void loginFail(LoginError errorCode, std::string userLogin, CNSocket* sock) {
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
|
||||
U8toU16(userLogin, resp.szID, sizeof(resp.szID));
|
||||
resp.iErrorCode = (int)errorCode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Login fail. Error code " << (int)errorCode << std::endl;
|
||||
)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_LOGIN))
|
||||
return; // ignore the malformed packet
|
||||
|
||||
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
|
||||
// TODO: implement better way of sending credentials
|
||||
std::string userLogin((char*)login->szCookie_TEGid);
|
||||
std::string userPassword((char*)login->szCookie_authid);
|
||||
|
||||
/*
|
||||
* Sometimes the client sends garbage cookie data.
|
||||
* Validate it as normal credentials instead of using a length check before falling back.
|
||||
*/
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
/*
|
||||
* The std::string -> char* -> std::string maneuver should remove any
|
||||
* trailing garbage after the null terminator.
|
||||
*/
|
||||
userLogin = std::string(U16toU8(login->szID).c_str());
|
||||
userPassword = std::string(U16toU8(login->szPassword).c_str());
|
||||
}
|
||||
|
||||
// the client inserts a "\n" in the password if you press enter key in the middle of the password
|
||||
// (not at the start or the end of the password field)
|
||||
if (int(userPassword.find("\n")) > 0)
|
||||
userPassword.erase(userPassword.find("\n"), 1);
|
||||
|
||||
// check regex
|
||||
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
|
||||
// send a custom error message
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
std::string text = "Invalid login or password\n";
|
||||
text += "Login has to be 4 - 32 characters long and can't contain special characters other than dash and underscore\n";
|
||||
text += "Password has to be 8 - 32 characters long";
|
||||
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
msg.iDuringTime = 15;
|
||||
sock->sendPacket((void*)&msg, P_FE2CL_GM_REP_PC_ANNOUNCE, sizeof(sP_FE2CL_GM_REP_PC_ANNOUNCE));
|
||||
|
||||
// we still have to send login fail to prevent softlock
|
||||
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
|
||||
}
|
||||
|
||||
|
||||
Database::Account findUser = {};
|
||||
Database::findAccount(&findUser, userLogin);
|
||||
|
||||
// account was not found
|
||||
if (findUser.AccountID == 0)
|
||||
return newAccount(sock, userLogin, userPassword, login->iClientVerC);
|
||||
|
||||
if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword))
|
||||
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
|
||||
|
||||
// is the account banned
|
||||
if (findUser.BannedUntil > getTimestamp()) {
|
||||
// send a custom error message
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
||||
|
||||
// ceiling devision
|
||||
int64_t remainingDays = (findUser.BannedUntil-getTimestamp()) / 86400 + ((findUser.BannedUntil - getTimestamp()) % 86400 != 0);
|
||||
|
||||
std::string text = "Your account has been banned. \nReason: ";
|
||||
text += findUser.BanReason;
|
||||
text += "\nBan expires in " + std::to_string(remainingDays) + " day";
|
||||
if (remainingDays > 1)
|
||||
text += "s";
|
||||
|
||||
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
msg.iDuringTime = 99999999;
|
||||
sock->sendPacket((void*)&msg, P_FE2CL_GM_REP_PC_ANNOUNCE, sizeof(sP_FE2CL_GM_REP_PC_ANNOUNCE));
|
||||
// don't send fail packet
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* calling this here to timestamp login attempt,
|
||||
* in order to make duplicate exit sanity check work
|
||||
*/
|
||||
Database::updateSelected(findUser.AccountID, findUser.Selected);
|
||||
|
||||
if (CNLoginServer::isAccountInUse(findUser.AccountID))
|
||||
return loginFail(LoginError::ID_ALREADY_IN_USE, userLogin, sock);
|
||||
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = findUser.AccountID;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
std::vector<sP_LS2CL_REP_CHAR_INFO> characters;
|
||||
Database::getCharInfo(&characters, loginSessions[sock].userID);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp);
|
||||
memcpy(resp.szID, login->szID, sizeof(login->szID));
|
||||
|
||||
resp.iCharCount = characters.size();
|
||||
resp.iSlotNum = findUser.Selected;
|
||||
resp.iPaymentFlag = 1;
|
||||
resp.iOpenBetaFlag = 0;
|
||||
resp.uiSvrTime = getTime();
|
||||
|
||||
#ifdef ACADEMY
|
||||
// do not prompt player to "enable freechat", which would only close the client
|
||||
resp.iChatEnabled = 1;
|
||||
#endif
|
||||
|
||||
// send the resp in with original key
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||
)
|
||||
|
||||
if (resp.iCharCount == 0)
|
||||
return;
|
||||
|
||||
// now send the characters :)
|
||||
std::vector<sP_LS2CL_REP_CHAR_INFO>::iterator it;
|
||||
for (it = characters.begin(); it != characters.end(); it++)
|
||||
sock->sendPacket((void*)&*it, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
|
||||
|
||||
DEBUGLOG(
|
||||
std::string message = "Login Server: Loaded " + std::to_string(resp.iCharCount) + " character";
|
||||
if ((int)resp.iCharCount > 1)
|
||||
message += "s";
|
||||
std::cout << message << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC) {
|
||||
|
||||
int userID = Database::addAccount(userLogin, userPassword);
|
||||
// if query somehow failed
|
||||
if (userID == 0)
|
||||
return loginFail(LoginError::DATABASE_ERROR, userLogin, sock);
|
||||
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = userID;
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp);
|
||||
U8toU16(userLogin, resp.szID, sizeof(resp.szID));
|
||||
|
||||
resp.iCharCount = 0;
|
||||
resp.iSlotNum = 1;
|
||||
resp.iPaymentFlag = 1;
|
||||
resp.iOpenBetaFlag = 0;
|
||||
resp.uiSvrTime = getTime();
|
||||
|
||||
// send the resp in with original key
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), clientVerC, 1));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: New account. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::nameCheck(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHECK_CHAR_NAME))
|
||||
return;
|
||||
|
||||
// responding to this packet only makes the client send the next packet (either name save or name change)
|
||||
// so we're always sending SUCC here and actually validating the name when the next packet arrives
|
||||
|
||||
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC, resp);
|
||||
memcpy(resp.szFirstName, nameCheck->szFirstName, sizeof(resp.szFirstName));
|
||||
memcpy(resp.szLastName, nameCheck->szLastName, sizeof(resp.szLastName));
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC));
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
}
|
||||
|
||||
void invalidCharacter(CNSocket* sock) {
|
||||
INITSTRUCT(sP_LS2CL_REP_SHARD_SELECT_FAIL, fail);
|
||||
fail.iErrorCode = 2;
|
||||
sock->sendPacket((void*)&fail, P_LS2CL_REP_SHARD_SELECT_FAIL, sizeof(sP_LS2CL_REP_SHARD_SELECT_FAIL));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Selected character error" << std::endl;
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_SAVE_CHAR_NAME* save = (sP_CL2LS_REQ_SAVE_CHAR_NAME*)data->buf;
|
||||
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
|
||||
|
||||
int errorCode = 0;
|
||||
if (!CNLoginServer::isCharacterNameGood(U16toU8(save->szFirstName), U16toU8(save->szLastName))) {
|
||||
errorCode = 4;
|
||||
} else if (!Database::isNameFree(U16toU8(save->szFirstName), U16toU8(save->szLastName))) {
|
||||
errorCode = 1;
|
||||
}
|
||||
|
||||
if (errorCode != 0) {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
|
||||
resp.iErrorCode = errorCode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_FAIL, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: name check fail. Error code " << errorCode << std::endl;
|
||||
)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
|
||||
return invalidCharacter(sock);
|
||||
|
||||
resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
|
||||
// if query somehow failed
|
||||
if (resp.iPC_UID == 0) {
|
||||
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
|
||||
return invalidCharacter(sock);
|
||||
}
|
||||
resp.iSlotNum = save->iSlotNum;
|
||||
resp.iGender = save->iGender;
|
||||
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(resp.szLastName));
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC));
|
||||
|
||||
Database::updateSelected(loginSessions[sock].userID, save->iSlotNum);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: new character created" << std::endl;
|
||||
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
||||
std::cout << "\tName: " << U16toU8(save->szFirstName) << " " << U16toU8(save->szLastName) << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
bool validateCharacterCreation(sP_CL2LS_REQ_CHAR_CREATE* character) {
|
||||
|
||||
// all the values have been determined from analyzing client code and xdt
|
||||
// and double checked using cheat engine
|
||||
|
||||
// check base parameters
|
||||
sPCStyle* style = &character->PCStyle;
|
||||
if (!(style->iBody >= 0 && style->iBody <= 2 &&
|
||||
style->iEyeColor >= 1 && style->iEyeColor <= 5 &&
|
||||
style->iGender >= 1 && style->iGender <= 2 &&
|
||||
style->iHairColor >= 1 && style->iHairColor <= 18) &&
|
||||
style->iHeight >= 0 && style->iHeight <= 4 &&
|
||||
style->iNameCheck >= 0 && style->iNameCheck <= 2 &&
|
||||
style->iSkinColor >= 1 && style->iSkinColor <= 12)
|
||||
return false;
|
||||
|
||||
// facestyle and hairstyle are gender dependent
|
||||
if (!(style->iGender == 1 && style->iFaceStyle >= 1 && style->iFaceStyle <= 5 && style->iHairStyle >= 1 && style->iHairStyle <= 23) &&
|
||||
!(style->iGender == 2 && style->iFaceStyle >= 6 && style->iFaceStyle <= 10 && style->iHairStyle >= 25 && style->iHairStyle <= 45))
|
||||
return false;
|
||||
|
||||
// validate items
|
||||
std::pair<int32_t, int32_t> items[3];
|
||||
items[0] = std::make_pair(character->sOn_Item.iEquipUBID, 1);
|
||||
items[1] = std::make_pair(character->sOn_Item.iEquipLBID, 2);
|
||||
items[2] = std::make_pair(character->sOn_Item.iEquipFootID, 3);
|
||||
// once we have a static database perhaps we can check for the exact char creation items,
|
||||
// for now only checking if it's a valid lvl1 item
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (Items::ItemData.find(items[i]) == Items::ItemData.end() || Items::ItemData[items[i]].level != 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNLoginServer::characterCreate(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_CREATE))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_CREATE* character = (sP_CL2LS_REQ_CHAR_CREATE*)data->buf;
|
||||
|
||||
if (!validateCharacterCreation(character))
|
||||
{
|
||||
std::cout << "[WARN] Login Server: invalid CHAR_CREATE packet!" << std::endl;
|
||||
return invalidCharacter(sock);
|
||||
}
|
||||
if (!Database::finishCharacter(character, loginSessions[sock].userID))
|
||||
{
|
||||
std::cout << "[WARN] Login Server: Database failed to finish character creation!" << std::endl;
|
||||
return invalidCharacter(sock);
|
||||
}
|
||||
|
||||
Player player = {};
|
||||
Database::getPlayer(&player, character->PCStyle.iPC_UID);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_CREATE_SUCC, resp);
|
||||
resp.sPC_Style = player.PCStyle;
|
||||
resp.sPC_Style2 = player.PCStyle2;
|
||||
resp.iLevel = player.level;
|
||||
resp.sOn_Item.iEquipUBID = player.Equip[1].iID;
|
||||
resp.sOn_Item.iEquipLBID = player.Equip[2].iID;
|
||||
resp.sOn_Item.iEquipFootID = player.Equip[3].iID;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_CREATE_SUCC, sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC));
|
||||
Database::updateSelected(loginSessions[sock].userID, player.slot);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Character creation completed" << std::endl;
|
||||
std::cout << "\tPC_UID: " << character->PCStyle.iPC_UID << std::endl;
|
||||
std::cout << "\tNameCheck: " << (int)character->PCStyle.iNameCheck << std::endl;
|
||||
std::cout << "\tName: " << U16toU8(character->PCStyle.szFirstName) << " " << U16toU8(character->PCStyle.szLastName) << std::endl;
|
||||
std::cout << "\tGender: " << (int)character->PCStyle.iGender << std::endl;
|
||||
std::cout << "\tFace: " << (int)character->PCStyle.iFaceStyle << std::endl;
|
||||
std::cout << "\tHair: " << (int)character->PCStyle.iHairStyle << std::endl;
|
||||
std::cout << "\tHair Color: " << (int)character->PCStyle.iHairColor << std::endl;
|
||||
std::cout << "\tSkin Color: " << (int)character->PCStyle.iSkinColor << std::endl;
|
||||
std::cout << "\tEye Color: " << (int)character->PCStyle.iEyeColor << std::endl;
|
||||
std::cout << "\tHeight: " << (int)character->PCStyle.iHeight << std::endl;
|
||||
std::cout << "\tBody: " << (int)character->PCStyle.iBody << std::endl;
|
||||
std::cout << "\tClass: " << (int)character->PCStyle.iClass << std::endl;
|
||||
std::cout << "\tiEquipUBID: " << (int)character->sOn_Item.iEquipUBID << std::endl;
|
||||
std::cout << "\tiEquipLBID: " << (int)character->sOn_Item.iEquipLBID << std::endl;
|
||||
std::cout << "\tiEquipFootID: " << (int)character->sOn_Item.iEquipFootID << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::characterDelete(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_DELETE))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_DELETE* del = (sP_CL2LS_REQ_CHAR_DELETE*)data->buf;
|
||||
|
||||
int removedSlot = Database::deleteCharacter(del->iPC_UID, loginSessions[sock].userID);
|
||||
if (removedSlot == 0)
|
||||
return invalidCharacter(sock);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_SUCC, resp);
|
||||
resp.iSlotNum = removedSlot;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_SUCC, sizeof(sP_LS2CL_REP_CHAR_DELETE_SUCC));
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Character [" << del->iPC_UID << "] deleted" << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_SELECT))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHAR_SELECT* selection = (sP_CL2LS_REQ_CHAR_SELECT*)data->buf;
|
||||
// we're doing a small hack and immediately send SHARD_SELECT_SUCC
|
||||
INITSTRUCT(sP_LS2CL_REP_SHARD_SELECT_SUCC, resp);
|
||||
|
||||
if (!Database::validateCharacter(selection->iPC_UID, loginSessions[sock].userID))
|
||||
return invalidCharacter(sock);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Selected character [" << selection->iPC_UID << "]" << std::endl;
|
||||
std::cout << "Connecting to shard server" << std::endl;
|
||||
)
|
||||
|
||||
const char* shard_ip = settings::SHARDSERVERIP.c_str();
|
||||
|
||||
/*
|
||||
* Work around the issue of not being able to connect to a local server if
|
||||
* the shard IP has been configured to an address the local machine can't
|
||||
* reach itself from.
|
||||
*/
|
||||
if (sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
|
||||
shard_ip = "127.0.0.1";
|
||||
|
||||
memcpy(resp.g_FE_ServerIP, shard_ip, strlen(shard_ip));
|
||||
|
||||
resp.g_FE_ServerIP[strlen(shard_ip)] = '\0';
|
||||
resp.g_FE_ServerPort = settings::SHARDPORT;
|
||||
|
||||
// pass player to CNSharedData
|
||||
Player passPlayer = {};
|
||||
Database::getPlayer(&passPlayer, selection->iPC_UID);
|
||||
// this should never happen but for extra safety
|
||||
if (passPlayer.iID == 0)
|
||||
return invalidCharacter(sock);
|
||||
|
||||
passPlayer.FEKey = sock->getFEKey();
|
||||
resp.iEnterSerialKey = passPlayer.iID;
|
||||
CNSharedData::setPlayer(resp.iEnterSerialKey, passPlayer);
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_SHARD_SELECT_SUCC, sizeof(sP_LS2CL_REP_SHARD_SELECT_SUCC));
|
||||
|
||||
// update current slot in DB
|
||||
Database::updateSelected(loginSessions[sock].userID, passPlayer.slot);
|
||||
}
|
||||
|
||||
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_TUTOR))
|
||||
return;
|
||||
sP_CL2LS_REQ_SAVE_CHAR_TUTOR* save = (sP_CL2LS_REQ_SAVE_CHAR_TUTOR*)data->buf;
|
||||
|
||||
if (!Database::finishTutorial(save->iPC_UID, loginSessions[sock].userID))
|
||||
return invalidCharacter(sock);
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
// no response here
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Character [" << save->iPC_UID << "] completed tutorial" << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_CHANGE_CHAR_NAME))
|
||||
return;
|
||||
|
||||
sP_CL2LS_REQ_CHANGE_CHAR_NAME* save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
|
||||
|
||||
int errorCode = 0;
|
||||
if (!CNLoginServer::isCharacterNameGood(U16toU8(save->szFirstName), U16toU8(save->szLastName))) {
|
||||
errorCode = 4;
|
||||
}
|
||||
else if (!Database::isNameFree(U16toU8(save->szFirstName), U16toU8(save->szLastName))) {
|
||||
errorCode = 1;
|
||||
}
|
||||
|
||||
if (errorCode != 0) {
|
||||
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
|
||||
resp.iErrorCode = errorCode;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_FAIL, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: name check fail. Error code " << errorCode << std::endl;
|
||||
)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Database::changeName(save, loginSessions[sock].userID))
|
||||
return invalidCharacter(sock);
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
|
||||
resp.iPC_UID = save->iPCUID;
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
||||
memcpy(resp.szLastName, save->szLastName, sizeof(resp.szLastName));
|
||||
resp.iSlotNum = save->iSlotNum;
|
||||
|
||||
loginSessions[sock].lastHeartbeat = getTime();
|
||||
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
|
||||
std::cout << "\tNew name: " << U16toU8(save->szFirstName) << " " << U16toU8(save->szLastName) << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
void CNLoginServer::duplicateExit(CNSocket* sock, CNPacketData* data) {
|
||||
if (data->size != sizeof(sP_CL2LS_REQ_PC_EXIT_DUPLICATE))
|
||||
return;
|
||||
|
||||
// TODO: FIX THIS PACKET
|
||||
|
||||
sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf;
|
||||
Database::Account account = {};
|
||||
Database::findAccount(&account, U16toU8(exit->szID));
|
||||
|
||||
// sanity check
|
||||
if (account.AccountID == 0) {
|
||||
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
exitDuplicate(account.AccountID);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region connections
|
||||
void CNLoginServer::newConnection(CNSocket* cns) {
|
||||
cns->setActiveKey(SOCKETKEY_E); // by default they accept keys encrypted with the default key
|
||||
}
|
||||
|
||||
void CNLoginServer::killConnection(CNSocket* cns) {
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Account [" << loginSessions[cns].userID << "] disconnected from login server" << std::endl;
|
||||
)
|
||||
loginSessions.erase(cns);
|
||||
}
|
||||
|
||||
void CNLoginServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
static time_t lastCheck = 0;
|
||||
|
||||
if (currTime - lastCheck < 16000)
|
||||
return;
|
||||
lastCheck = currTime;
|
||||
|
||||
for (auto& pair : loginSessions) {
|
||||
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 32000) {
|
||||
pair.first->kill();
|
||||
continue;
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_LS2CL_REQ_LIVE_CHECK, pkt);
|
||||
pair.first->sendPacket((void*)&pkt, P_LS2CL_REQ_LIVE_CHECK, sizeof(sP_LS2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region helperMethods
|
||||
bool CNLoginServer::isAccountInUse(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CNLoginServer::exitDuplicate(int accountId) {
|
||||
std::map<CNSocket*, CNLoginData>::iterator it;
|
||||
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
|
||||
if (it->second.userID == accountId) {
|
||||
CNSocket* sock = it->first;
|
||||
INITSTRUCT(sP_LS2CL_REP_PC_EXIT_DUPLICATE, resp);
|
||||
resp.iErrorCode = 0;
|
||||
sock->sendPacket((void*)&resp, P_LS2CL_REP_PC_EXIT_DUPLICATE, sizeof(sP_LS2CL_REP_PC_EXIT_DUPLICATE));
|
||||
sock->kill();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
|
||||
std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
|
||||
std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
|
||||
|
||||
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
|
||||
}
|
||||
|
||||
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) {
|
||||
return BCrypt::validatePassword(tryPassword, actualPassword);
|
||||
}
|
||||
|
||||
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
|
||||
//Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name)
|
||||
std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)");
|
||||
std::regex lastnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)");
|
||||
return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck));
|
||||
}
|
||||
#pragma endregion
|
54
src/servers/CNLoginServer.hpp
Normal file
54
src/servers/CNLoginServer.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
#include "Player.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
struct CNLoginData {
|
||||
int userID;
|
||||
time_t lastHeartbeat;
|
||||
};
|
||||
|
||||
enum class LoginError {
|
||||
DATABASE_ERROR = 0,
|
||||
ID_DOESNT_EXIST = 1,
|
||||
ID_AND_PASSWORD_DO_NOT_MATCH = 2,
|
||||
ID_ALREADY_IN_USE = 3,
|
||||
LOGIN_ERROR = 4,
|
||||
CLIENT_VERSION_OUTDATED = 6,
|
||||
YOU_ARE_NOT_AN_AUTHORIZED_BETA_TESTER = 7,
|
||||
AUTHENTICATION_CONNECTION_ERROR = 8,
|
||||
UPDATED_EUALA_REQUIRED = 9
|
||||
};
|
||||
|
||||
// WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static
|
||||
class CNLoginServer : public CNServer {
|
||||
private:
|
||||
static void handlePacket(CNSocket* sock, CNPacketData* data);
|
||||
static std::map<CNSocket*, CNLoginData> loginSessions;
|
||||
|
||||
static void login(CNSocket* sock, CNPacketData* data);
|
||||
static void nameCheck(CNSocket* sock, CNPacketData* data);
|
||||
static void nameSave(CNSocket* sock, CNPacketData* data);
|
||||
static void characterCreate(CNSocket* sock, CNPacketData* data);
|
||||
static void characterDelete(CNSocket* sock, CNPacketData* data);
|
||||
static void characterSelect(CNSocket* sock, CNPacketData* data);
|
||||
static void finishTutorial(CNSocket* sock, CNPacketData* data);
|
||||
static void changeName(CNSocket* sock, CNPacketData* data);
|
||||
static void duplicateExit(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
static bool isLoginDataGood(std::string login, std::string password);
|
||||
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
|
||||
static bool isAccountInUse(int accountId);
|
||||
static bool isCharacterNameGood(std::string Firstname, std::string Lastname);
|
||||
static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC);
|
||||
// returns true if success
|
||||
static bool exitDuplicate(int accountId);
|
||||
public:
|
||||
CNLoginServer(uint16_t p);
|
||||
|
||||
void newConnection(CNSocket* cns);
|
||||
void killConnection(CNSocket* cns);
|
||||
void onStep();
|
||||
};
|
118
src/servers/CNShardServer.cpp
Normal file
118
src/servers/CNShardServer.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "MobAI.hpp"
|
||||
#include "core/CNShared.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "TableData.hpp" // for flush()
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
|
||||
std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
|
||||
std::list<TimerEvent> CNShardServer::Timers;
|
||||
|
||||
CNShardServer::CNShardServer(uint16_t p) {
|
||||
port = p;
|
||||
pHandler = &CNShardServer::handlePacket;
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
|
||||
init();
|
||||
|
||||
if (settings::MONITORENABLED)
|
||||
fds.push_back({Monitor::init(), POLLIN});
|
||||
}
|
||||
|
||||
void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
printPacket(data, CL2FE);
|
||||
|
||||
if (ShardPackets.find(data->type) != ShardPackets.end())
|
||||
ShardPackets[data->type](sock, data);
|
||||
else if (settings::VERBOSITY > 0)
|
||||
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Packets::p2str(CL2FE, data->type) << " (" << data->type << ")" << std::endl;
|
||||
|
||||
if (PlayerManager::players.find(sock) != PlayerManager::players.end())
|
||||
PlayerManager::players[sock]->lastHeartbeat = getTime();
|
||||
}
|
||||
|
||||
void CNShardServer::keepAliveTimer(CNServer* serv, time_t currTime) {
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->lastHeartbeat != 0 && currTime - pair.second->lastHeartbeat > settings::TIMEOUT) {
|
||||
// if the client hasn't responded in 60 seconds, its a dead connection so throw it out
|
||||
pair.first->kill();
|
||||
} else if (pair.second->lastHeartbeat != 0 && currTime - pair.second->lastHeartbeat > settings::TIMEOUT/2) {
|
||||
// if the player hasn't responded in 30 seconds, send a live check
|
||||
INITSTRUCT(sP_FE2CL_REQ_LIVE_CHECK, data);
|
||||
pair.first->sendPacket((void*)&data, P_FE2CL_REQ_LIVE_CHECK, sizeof(sP_FE2CL_REQ_LIVE_CHECK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
|
||||
if (PlayerManager::players.empty())
|
||||
return;
|
||||
|
||||
std::cout << "[INFO] Saving " << PlayerManager::players.size() << " players to DB..." << std::endl;
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
Database::updatePlayer(pair.second);
|
||||
}
|
||||
|
||||
TableData::flush();
|
||||
std::cout << "[INFO] Done." << std::endl;
|
||||
}
|
||||
|
||||
bool CNShardServer::checkExtraSockets(int i) {
|
||||
return Monitor::acceptConnection(fds[i].fd, fds[i].revents);
|
||||
}
|
||||
|
||||
void CNShardServer::newConnection(CNSocket* cns) {
|
||||
cns->setActiveKey(SOCKETKEY_E); // by default they accept keys encrypted with the default key
|
||||
}
|
||||
|
||||
// must be static to be called from PlayerManager::exitDuplicate()
|
||||
void CNShardServer::_killConnection(CNSocket* cns) {
|
||||
// check if the player ever sent a REQ_PC_ENTER
|
||||
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 CNSharedData
|
||||
CNSharedData::erasePlayer(key);
|
||||
}
|
||||
|
||||
void CNShardServer::killConnection(CNSocket *cns) {
|
||||
_killConnection(cns);
|
||||
}
|
||||
|
||||
// flush the DB when terminating the server
|
||||
void CNShardServer::kill() {
|
||||
periodicSaveTimer(nullptr, 0);
|
||||
CNServer::kill();
|
||||
}
|
||||
|
||||
void CNShardServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
|
||||
for (TimerEvent& event : Timers) {
|
||||
if (event.scheduledEvent == 0) {
|
||||
// event hasn't been queued yet, go ahead and do that
|
||||
event.scheduledEvent = currTime + event.delta;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.scheduledEvent < currTime) {
|
||||
// timer needs to be called
|
||||
event.handlr(this, currTime);
|
||||
event.scheduledEvent = currTime + event.delta;
|
||||
}
|
||||
}
|
||||
}
|
30
src/servers/CNShardServer.hpp
Normal file
30
src/servers/CNShardServer.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
||||
|
||||
class CNShardServer : public CNServer {
|
||||
private:
|
||||
static void handlePacket(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
static void keepAliveTimer(CNServer*, time_t);
|
||||
static void periodicSaveTimer(CNServer* serv, time_t currTime);
|
||||
|
||||
public:
|
||||
static std::map<uint32_t, PacketHandler> ShardPackets;
|
||||
static std::list<TimerEvent> Timers;
|
||||
|
||||
CNShardServer(uint16_t p);
|
||||
|
||||
static void _killConnection(CNSocket *cns);
|
||||
|
||||
bool checkExtraSockets(int i);
|
||||
void newConnection(CNSocket* cns);
|
||||
void killConnection(CNSocket* cns);
|
||||
void kill();
|
||||
void onStep();
|
||||
};
|
166
src/servers/Monitor.cpp
Normal file
166
src/servers/Monitor.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Chat.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
static SOCKET listener;
|
||||
static std::mutex sockLock; // guards socket list
|
||||
static std::list<SOCKET> sockets;
|
||||
static sockaddr_in address;
|
||||
|
||||
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
|
||||
int n = 0;
|
||||
int sock = *it;
|
||||
|
||||
while (n < len) {
|
||||
n += send(sock, buff+n, len-n, 0);
|
||||
if (SOCKETERROR(n)) {
|
||||
printSocketError("send");
|
||||
|
||||
#ifdef _WIN32
|
||||
shutdown(sock, SD_BOTH);
|
||||
closesocket(sock);
|
||||
#else
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
close(sock);
|
||||
#endif
|
||||
|
||||
std::cout << "[INFO] Disconnected a monitor" << std::endl;
|
||||
|
||||
it = sockets.erase(it);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tick(CNServer *serv, time_t delta) {
|
||||
std::lock_guard<std::mutex> lock(sockLock);
|
||||
char buff[256];
|
||||
int n;
|
||||
|
||||
auto it = sockets.begin();
|
||||
outer:
|
||||
while (it != sockets.end()) {
|
||||
if (!transmit(it, (char*)"begin\n", 6))
|
||||
continue;
|
||||
|
||||
// player
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->hidden)
|
||||
continue;
|
||||
|
||||
n = std::snprintf(buff, sizeof(buff), "player %d %d %s\n",
|
||||
pair.second->x, pair.second->y,
|
||||
PlayerManager::getPlayerName(pair.second, false).c_str());
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
goto outer;
|
||||
}
|
||||
|
||||
// chat
|
||||
for (auto& str : Chat::dump) {
|
||||
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
goto outer;
|
||||
}
|
||||
|
||||
if (!transmit(it, (char*)"end\n", 4))
|
||||
continue;
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
Chat::dump.clear();
|
||||
}
|
||||
|
||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||
socklen_t len = sizeof(address);
|
||||
|
||||
if (!settings::MONITORENABLED)
|
||||
return false;
|
||||
|
||||
if (fd != listener)
|
||||
return false;
|
||||
|
||||
if (revents & ~POLLIN) {
|
||||
std::cout << "[FATAL] Error on monitor listener?" << std::endl;
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
int sock = accept(listener, (struct sockaddr*)&address, &len);
|
||||
if (SOCKETERROR(sock)) {
|
||||
printSocketError("accept");
|
||||
return true;
|
||||
}
|
||||
|
||||
setSockNonblocking(listener, sock);
|
||||
|
||||
std::cout << "[INFO] New monitor connection from " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sockLock);
|
||||
|
||||
sockets.push_back(sock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKET Monitor::init() {
|
||||
listener = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (SOCKETERROR(listener)) {
|
||||
std::cout << "Failed to create monitor socket" << std::endl;
|
||||
printSocketError("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const char opt = 1;
|
||||
#else
|
||||
int opt = 1;
|
||||
#endif
|
||||
if (SOCKETERROR(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))) {
|
||||
std::cout << "Failed to set SO_REUSEADDR on monitor socket" << std::endl;
|
||||
printSocketError("setsockopt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(settings::MONITORPORT);
|
||||
|
||||
if (SOCKETERROR(bind(listener, (struct sockaddr*)&address, sizeof(address)))) {
|
||||
std::cout << "Failed to bind to monitor port" << std::endl;
|
||||
printSocketError("bind");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (SOCKETERROR(listen(listener, SOMAXCONN))) {
|
||||
std::cout << "Failed to listen on monitor port" << std::endl;
|
||||
printSocketError("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(listener, FIONBIO, &mode) != 0) {
|
||||
#else
|
||||
if (fcntl(listener, F_SETFL, (fcntl(listener, F_GETFL, 0) | O_NONBLOCK)) != 0) {
|
||||
#endif
|
||||
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
|
||||
printSocketError("fcntl");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::cout << "Monitor listening on *:" << settings::MONITORPORT << std::endl;
|
||||
|
||||
REGISTER_SHARD_TIMER(tick, settings::MONITORINTERVAL);
|
||||
|
||||
return listener;
|
||||
}
|
11
src/servers/Monitor.hpp
Normal file
11
src/servers/Monitor.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/Core.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
namespace Monitor {
|
||||
SOCKET init();
|
||||
bool acceptConnection(SOCKET, uint16_t);
|
||||
};
|
Reference in New Issue
Block a user