[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:
2021-03-17 20:07:40 +01:00
parent e9bc2fe561
commit a55a34e09a
47 changed files with 89 additions and 96 deletions

View 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

View 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();
};

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

View 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
View 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
View File

@@ -0,0 +1,11 @@
#pragma once
#include "core/Core.hpp"
#include <list>
#include <mutex>
namespace Monitor {
SOCKET init();
bool acceptConnection(SOCKET, uint16_t);
};