/* * Original work: Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * RakNet License.txt file in the licenses directory of this source tree. An additional grant * of patent rights can be found in the RakNet Patents.txt file in the same directory. * * * Modified work: Copyright (c) 2017-2021, SLikeSoft UG (haftungsbeschränkt) * * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style * license found in the license.txt file in the root directory of this source tree. */ #include "Lobby2Server.h" #include "slikenet/assert.h" #include "slikenet/MessageIdentifiers.h" //#define __INTEGRATE_LOBBY2_WITH_ROOMS_PLUGIN #ifdef __INTEGRATE_LOBBY2_WITH_ROOMS_PLUGIN #include "RoomsPlugin.h" #endif using namespace SLNet; int Lobby2Server::UserCompByUsername( const RakString &key, Lobby2Server::User * const &data ) { if (key < data->userName) return -1; if (key==data->userName) return 0; return 1; } Lobby2Server::Lobby2Server() { DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); roomsPlugin=0; roomsPluginAddress=UNASSIGNED_SYSTEM_ADDRESS; } Lobby2Server::~Lobby2Server() { Clear(); } void Lobby2Server::SendMsg(Lobby2Message *msg, const DataStructures::List &recipients) { SLNet::BitStream bs; bs.Write((MessageID)ID_LOBBY2_SEND_MESSAGE); bs.Write((MessageID)msg->GetID()); msg->Serialize(true,true,&bs); SendUnifiedToMultiple(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, recipients); } void Lobby2Server::Update(void) { while (threadActionQueue.Size()) { threadActionQueueMutex.Lock(); if (threadActionQueue.Size()) { ThreadAction ta = threadActionQueue.Pop(); threadActionQueueMutex.Unlock(); if (ta.action==L2MID_Client_Logoff) { OnLogoff(&ta.command, false); } else if (ta.action==L2MID_Client_Login) { OnLogin(&ta.command, false); } else if (ta.action==L2MID_Client_ChangeHandle) { OnChangeHandle(&ta.command, false); } } else { threadActionQueueMutex.Unlock(); break; } } if (threadPool.HasOutputFast() && threadPool.HasOutput()) { Lobby2ServerCommand c = threadPool.GetOutput(); c.lobby2Message->ServerPostDBMemoryImpl(this, c.callingUserName); if (c.returnToSender) { for (unsigned long i=0; i < callbacks.Size(); i++) { if (c.lobby2Message->callbackId==(uint32_t)-1 || c.lobby2Message->callbackId==callbacks[i]->callbackId) c.lobby2Message->CallCallback(callbacks[i]); } SLNet::BitStream bs; bs.Write((MessageID)ID_LOBBY2_SEND_MESSAGE); bs.Write((MessageID)c.lobby2Message->GetID()); c.lobby2Message->Serialize(true,true,&bs); // Have the ID to send to, but not the address. The ID came from the thread, such as notifying another user if (c.callerSystemAddresses.Size()==0) { unsigned int i; if (c.callerUserId!=0) { for (i=0; i < users.Size(); i++) { if (users[i]->callerUserId==c.callerUserId) { c.callerSystemAddresses=users[i]->systemAddresses; c.callerGuids=users[i]->guids; /* if (c.requiredConnectionAddress!=UNASSIGNED_SYSTEM_ADDRESS) { // This message refers to another user that has to be logged on for it to be sent bool objectExists; unsigned int idx; idx = users.GetIndexFromKey(c.callerSystemAddress,&objectExists); if (!objectExists) { if (c.deallocMsgWhenDone) SLNet::OP_DELETE(c.lobby2Message, __FILE__, __LINE__); return; } } */ break; } } } if (c.callerSystemAddresses.Size()==0 && !c.callingUserName.IsEmpty()) { for (i=0; i < users.Size(); i++) { if (users[i]->callerUserId==c.callerUserId) { c.callerSystemAddresses=users[i]->systemAddresses; c.callerGuids=users[i]->guids; break; } } } } else { bool objectExists; unsigned int idx; idx = users.GetIndexFromKey(c.callingUserName,&objectExists); if (objectExists && !c.callingUserName.IsEmpty() && users[idx]->userName!=c.callingUserName) { // Different user, same IP address. Abort the send. if (c.deallocMsgWhenDone) SLNet::OP_DELETE(c.lobby2Message, __FILE__, __LINE__); return; } } SendUnifiedToMultiple(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, c.callerSystemAddresses); } if (c.deallocMsgWhenDone) SLNet::OP_DELETE(c.lobby2Message, __FILE__, __LINE__); } } PluginReceiveResult Lobby2Server::OnReceive(Packet *packet) { RakAssert(packet); switch (packet->data[0]) { case ID_LOBBY2_SEND_MESSAGE: OnMessage(packet); return RR_STOP_PROCESSING_AND_DEALLOCATE; } return RR_CONTINUE_PROCESSING; } void Lobby2Server::OnShutdown(void) { Clear(); } void Lobby2Server::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) { (void)rakNetGUID; (void)lostConnectionReason; unsigned int index = GetUserIndexBySystemAddress(systemAddress); // If systemAddress is a user, then notify his friends about him logging off if (index != static_cast(-1)) { bool found=false; User *user = users[index]; for (unsigned int i=0; i < user->systemAddresses.Size(); i++) { if (user->systemAddresses[i]==systemAddress) { found=true; user->systemAddresses.RemoveAtIndexFast(i); break; } } if (found && user->systemAddresses.Size()==0) { // Log this logoff due to closed connection Lobby2Message *lobby2Message = msgFactory->Alloc(L2MID_Client_Logoff); Lobby2ServerCommand command; command.lobby2Message=lobby2Message; command.deallocMsgWhenDone=true; command.returnToSender=true; command.callerUserId=users[index]->callerUserId; command.server=this; ExecuteCommand(&command); RemoveUser(index); } } } void Lobby2Server::OnMessage(Packet *packet) { SLNet::BitStream bs(packet->data,packet->length,false); bs.IgnoreBytes(1); // ID_LOBBY2_SEND_MESSAGE MessageID msgId; bs.Read(msgId); Lobby2MessageID lobby2MessageID = (Lobby2MessageID) msgId; unsigned int index; Lobby2Message *lobby2Message = msgFactory->Alloc(lobby2MessageID); if (lobby2Message) { lobby2Message->Serialize(false,false,&bs); Lobby2ServerCommand command; command.lobby2Message=lobby2Message; command.deallocMsgWhenDone=true; command.returnToSender=true; index=GetUserIndexBySystemAddress(packet->systemAddress); if (index != static_cast(-1)) { command.callingUserName=users[index]->userName; command.callerUserId=users[index]->callerUserId; } else { if (lobby2Message->RequiresLogin()) { SLNet::BitStream bs2; bs2.Write((MessageID)ID_LOBBY2_SEND_MESSAGE); bs2.Write((MessageID)lobby2Message->GetID()); lobby2Message->resultCode=L2RC_NOT_LOGGED_IN; lobby2Message->Serialize(true,true,&bs2); SendUnified(&bs2,packetPriority, RELIABLE_ORDERED, orderingChannel, packet->systemAddress, false); return; } command.callerUserId=0; } command.callerSystemAddresses.Push(packet->systemAddress,__FILE__,__LINE__); command.callerGuids.Push(packet->guid,__FILE__,__LINE__); command.server=this; ExecuteCommand(&command); } else { SLNet::BitStream out; out.Write((MessageID)ID_LOBBY2_SERVER_ERROR); out.Write((unsigned char) L2SE_UNKNOWN_MESSAGE_ID); out.Write((unsigned int) msgId); SendUnified(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, packet->systemAddress, false); } } void Lobby2Server::Clear(void) { ClearAdminAddresses(); ClearRankingAddresses(); ClearUsers(); ClearConnections(); threadPool.StopThreads(); RakAssert(threadPool.NumThreadsWorking()==0); unsigned i; Lobby2ServerCommand c; for (i=0; i < threadPool.InputSize(); i++) { c = threadPool.GetInputAtIndex(i); if (c.deallocMsgWhenDone && c.lobby2Message) SLNet::OP_DELETE(c.lobby2Message, __FILE__, __LINE__); } threadPool.ClearInput(); for (i=0; i < threadPool.OutputSize(); i++) { c = threadPool.GetOutputAtIndex(i); if (c.deallocMsgWhenDone && c.lobby2Message) SLNet::OP_DELETE(c.lobby2Message, __FILE__, __LINE__); } threadPool.ClearOutput(); threadActionQueueMutex.Lock(); threadActionQueue.Clear(__FILE__, __LINE__); threadActionQueueMutex.Unlock(); } void Lobby2Server::AddAdminAddress(SystemAddress addr) { adminAddresses.Insert(addr,addr,false, __FILE__, __LINE__ ); } bool Lobby2Server::HasAdminAddress(const DataStructures::List &addresses) { if (addresses.Size()==0) return true; unsigned int j; for (j=0; j < addresses.Size(); j++) { if (adminAddresses.HasData(addresses[j])) return true; } return false; } void Lobby2Server::RemoveAdminAddress(SystemAddress addr) { adminAddresses.RemoveIfExists(addr); } void Lobby2Server::ClearAdminAddresses(void) { adminAddresses.Clear(false, __FILE__, __LINE__); } void Lobby2Server::AddRankingAddress(SystemAddress addr) { rankingAddresses.Insert(addr,addr,false, __FILE__, __LINE__ ); } bool Lobby2Server::HasRankingAddress(const DataStructures::List &addresses) { if (addresses.Size()==0) return true; unsigned int j; for (j=0; j < addresses.Size(); j++) { if (rankingAddresses.HasData(addresses[j])) return true; } return false; } void Lobby2Server::RemoveRankingAddress(SystemAddress addr) { rankingAddresses.RemoveIfExists(addr); } void Lobby2Server::ClearRankingAddresses(void) { rankingAddresses.Clear(false, __FILE__, __LINE__); } void Lobby2Server::ExecuteCommand(Lobby2ServerCommand *command) { //SLNet::BitStream out; if (!command->lobby2Message->PrevalidateInput()) { SendMsg(command->lobby2Message, command->callerSystemAddresses); if (command->deallocMsgWhenDone) msgFactory->Dealloc(command->lobby2Message); return; } if (command->lobby2Message->RequiresAdmin() && !HasAdminAddress(command->callerSystemAddresses)) { command->lobby2Message->resultCode=L2RC_REQUIRES_ADMIN; SendMsg(command->lobby2Message, command->callerSystemAddresses); //SendUnifiedToMultiple(&out,packetPriority, RELIABLE_ORDERED, orderingChannel, command->callerSystemAddresses); if (command->deallocMsgWhenDone) msgFactory->Dealloc(command->lobby2Message); return; } if (command->lobby2Message->RequiresRankingPermission() && !HasRankingAddress(command->callerSystemAddresses)) { command->lobby2Message->resultCode=L2RC_REQUIRES_ADMIN; SendMsg(command->lobby2Message, command->callerSystemAddresses); //SendUnifiedToMultiple(&out,packetPriority, RELIABLE_ORDERED, orderingChannel, command->callerSystemAddresses); if (command->deallocMsgWhenDone) msgFactory->Dealloc(command->lobby2Message); return; } if (command->lobby2Message->ServerPreDBMemoryImpl(this, command->callingUserName)) { SendMsg(command->lobby2Message, command->callerSystemAddresses); if (command->deallocMsgWhenDone) msgFactory->Dealloc(command->lobby2Message); return; } command->server=this; AddInputCommand(*command); } void Lobby2Server::SetRoomsPlugin(RoomsPlugin *rp) { roomsPlugin=rp; roomsPluginAddress=UNASSIGNED_SYSTEM_ADDRESS; } void Lobby2Server::SetRoomsPluginAddress(SystemAddress address) { roomsPluginAddress=address; roomsPlugin=0; } void Lobby2Server::ClearUsers(void) { unsigned int i; for (i=0; i < users.Size(); i++) SLNet::OP_DELETE(users[i], __FILE__, __LINE__); users.Clear(false, __FILE__, __LINE__); } void Lobby2Server::LogoffFromRooms(User *user) { // Remove from the room too #if defined(__INTEGRATE_LOBBY2_WITH_ROOMS_PLUGIN) // Tell the rooms plugin about the logoff event if (roomsPlugin) { roomsPlugin->LogoffRoomsParticipant(user->userName, UNASSIGNED_SYSTEM_ADDRESS); } else if (roomsPluginAddress!=UNASSIGNED_SYSTEM_ADDRESS) { SLNet::BitStream bs; RoomsPlugin::SerializeLogoff(user->userName,&bs); SendUnified(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, roomsPluginAddress, false); } #endif } void Lobby2Server::SendRemoteLoginNotification(SLNet::RakString handle, const DataStructures::List& recipients) { Notification_Client_RemoteLogin notification; notification.handle=handle; notification.resultCode=L2RC_SUCCESS; SendMsg(¬ification, recipients); } void Lobby2Server::OnLogin(Lobby2ServerCommand *command, bool calledFromThread) { if (calledFromThread) { ThreadAction ta; ta.action=L2MID_Client_Login; ta.command=*command; threadActionQueueMutex.Lock(); threadActionQueue.Push(ta, __FILE__, __LINE__ ); threadActionQueueMutex.Unlock(); return; } bool objectExists; unsigned int insertionIndex = users.GetIndexFromKey(command->callingUserName, &objectExists); if (objectExists) { User * user = users[insertionIndex]; if (!user->allowMultipleLogins) { SendRemoteLoginNotification(user->userName, user->systemAddresses); LogoffFromRooms(user); // Already logged in from this system address. // Delete the existing entry, which will be reinserted. SLNet::OP_DELETE(user,_FILE_AND_LINE_); users.RemoveAtIndex(insertionIndex); } else { if (user->systemAddresses.GetIndexOf(command->callerSystemAddresses[0])==(unsigned int) -1) { // Just add system address and guid already in use to the list for this user user->systemAddresses.Push(command->callerSystemAddresses[0], __FILE__, __LINE__); user->guids.Push(command->callerGuids[0], __FILE__, __LINE__); } return; } } else { // Different username, from the same IP address or RakNet instance unsigned int idx2 = GetUserIndexByGUID(command->callerGuids[0]); unsigned int idx3 = GetUserIndexBySystemAddress(command->callerSystemAddresses[0]); if (idx2 != (unsigned int)-1) { User * user = users[idx2]; if (user->allowMultipleLogins) return; SendRemoteLoginNotification(user->userName, user->systemAddresses); LogoffFromRooms(user); SLNet::OP_DELETE(user,__FILE__,__LINE__); users.RemoveAtIndex(idx2); insertionIndex = users.GetIndexFromKey(command->callingUserName, &objectExists); } else if (idx3 != (unsigned int)-1) { User * user = users[idx3]; if (user->allowMultipleLogins) return; SendRemoteLoginNotification(user->userName, user->systemAddresses); LogoffFromRooms(user); SLNet::OP_DELETE(user,__FILE__,__LINE__); users.RemoveAtIndex(idx3); insertionIndex = users.GetIndexFromKey(command->callingUserName, &objectExists); } } User *user = SLNet::OP_NEW( __FILE__, __LINE__ ); user->userName=command->callingUserName; user->systemAddresses=command->callerSystemAddresses; user->guids=command->callerGuids; user->callerUserId=command->callerUserId; user->allowMultipleLogins=((Client_Login*)command->lobby2Message)->allowMultipleLogins; users.InsertAtIndex(user, insertionIndex, __FILE__, __LINE__ ); #if defined(__INTEGRATE_LOBBY2_WITH_ROOMS_PLUGIN) // Tell the rooms plugin about the login event if (roomsPlugin) { roomsPlugin->LoginRoomsParticipant(user->userName, user->systemAddresses[0], user->guids[0], UNASSIGNED_SYSTEM_ADDRESS); } else if (roomsPluginAddress!=UNASSIGNED_SYSTEM_ADDRESS) { SLNet::BitStream bs; RoomsPlugin::SerializeLogin(user->userName,user->systemAddresses[0], user->guids[0], &bs); SendUnified(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, roomsPluginAddress, false); } #endif } void Lobby2Server::OnLogoff(Lobby2ServerCommand *command, bool calledFromThread) { if (calledFromThread) { ThreadAction ta; ta.action=L2MID_Client_Logoff; ta.command=*command; threadActionQueueMutex.Lock(); threadActionQueue.Push(ta, __FILE__, __LINE__ ); threadActionQueueMutex.Unlock(); return; } RemoveUser(command->callingUserName); } void Lobby2Server::OnChangeHandle(Lobby2ServerCommand *command, bool calledFromThread) { if (calledFromThread) { ThreadAction ta; ta.action=L2MID_Client_ChangeHandle; ta.command=*command; threadActionQueueMutex.Lock(); threadActionQueue.Push(ta, __FILE__, __LINE__ ); threadActionQueueMutex.Unlock(); return; } unsigned int i; SLNet::RakString oldHandle; for (i=0; i < users.Size(); i++) { if (users[i]->callerUserId==command->callerUserId) { oldHandle=users[i]->userName; users[i]->userName=command->callingUserName; break; } } if (oldHandle.IsEmpty()) return; #if defined(__INTEGRATE_LOBBY2_WITH_ROOMS_PLUGIN) // Tell the rooms plugin about the handle change if (roomsPlugin) { roomsPlugin->ChangeHandle(oldHandle, command->callingUserName); } else if (roomsPluginAddress!=UNASSIGNED_SYSTEM_ADDRESS) { SLNet::BitStream bs; RoomsPlugin::SerializeChangeHandle(oldHandle,command->callingUserName,&bs); SendUnified(&bs,packetPriority, RELIABLE_ORDERED, orderingChannel, roomsPluginAddress, false); } #endif } void Lobby2Server::RemoveUser(RakString userName) { bool objectExists; unsigned int index = users.GetIndexFromKey(userName, &objectExists); if (objectExists) RemoveUser(index); } void Lobby2Server::RemoveUser(unsigned int index) { User *user = users[index]; Lobby2ServerCommand command; Notification_Friends_StatusChange *notification = (Notification_Friends_StatusChange *) GetMessageFactory()->Alloc(L2MID_Notification_Friends_StatusChange); notification->otherHandle=user->userName; notification->op=Notification_Friends_StatusChange::FRIEND_LOGGED_OFF; notification->resultCode=L2RC_SUCCESS; command.server=this; command.deallocMsgWhenDone=true; command.lobby2Message=notification; command.callerUserId=user->callerUserId; command.callingUserName=user->userName; ExecuteCommand(&command); unsigned i; i=0; threadPool.LockInput(); while (i < threadPool.InputSize()) { command = threadPool.GetInputAtIndex(i); if (command.lobby2Message->CancelOnDisconnect()&& command.callerSystemAddresses.Size()>0 && user->systemAddresses.GetIndexOf(command.callerSystemAddresses[0])!=(unsigned int)-1) { if (command.deallocMsgWhenDone) SLNet::OP_DELETE(command.lobby2Message, __FILE__, __LINE__); threadPool.RemoveInputAtIndex(i); } else i++; } threadPool.UnlockInput(); LogoffFromRooms(user); SLNet::OP_DELETE(user,__FILE__,__LINE__); users.RemoveAtIndex(index); } unsigned int Lobby2Server::GetUserIndexBySystemAddress(SystemAddress systemAddress) const { unsigned int idx1,idx2; for (idx1=0; idx1 < users.Size(); idx1++) { for (idx2=0; idx2 < users[idx1]->systemAddresses.Size(); idx2++) { if (users[idx1]->systemAddresses[idx2]==systemAddress) return idx1; } } // #med - review negative value cast to unsigned not really good return (unsigned int) -1; } unsigned int Lobby2Server::GetUserIndexByGUID(RakNetGUID guid) const { unsigned int idx1,idx2; for (idx1=0; idx1 < users.Size(); idx1++) { for (idx2=0; idx2 < users[idx1]->guids.Size(); idx2++) { if (users[idx1]->guids[idx2]==guid) return idx1; } } return (unsigned int) -1; } unsigned int Lobby2Server::GetUserIndexByUsername(SLNet::RakString userName) const { unsigned int idx; bool objectExists; idx = users.GetIndexFromKey(userName,&objectExists); if (objectExists) return idx; return (unsigned int) -1; } void Lobby2Server::StopThreads(void) { threadPool.StopThreads(); } void Lobby2Server::SetConfigurationProperties(ConfigurationProperties c) { configurationProperties=c; } const Lobby2Server::ConfigurationProperties *Lobby2Server::GetConfigurationProperties(void) const { return &configurationProperties; } void Lobby2Server::GetUserOnlineStatus(UsernameAndOnlineStatus &userInfo) const { unsigned int idx = GetUserIndexByUsername(userInfo.handle); if (idx != static_cast(-1)) { userInfo.isOnline=true; userInfo.presence=users[idx]->presence; } else { userInfo.isOnline=false; userInfo.presence.status=Lobby2Presence::NOT_ONLINE; userInfo.presence.isVisible=false; } } void Lobby2Server::SetPresence(const SLNet::Lobby2Presence &presence, SLNet::RakString userHandle) { unsigned int index = GetUserIndexByUsername(userHandle); if (index != static_cast(-1)) { User *user = users[index]; user->presence=presence; // Push notify presence update to friends Lobby2ServerCommand command; Notification_Friends_PresenceUpdate *notification = (Notification_Friends_PresenceUpdate *) GetMessageFactory()->Alloc(L2MID_Notification_Friends_PresenceUpdate); notification->newPresence=presence; notification->otherHandle=user->userName; notification->resultCode=L2RC_SUCCESS; command.server=this; command.deallocMsgWhenDone=true; command.lobby2Message=notification; command.callerUserId=user->callerUserId; command.callingUserName=user->userName; ExecuteCommand(&command); } } void Lobby2Server::GetPresence(SLNet::Lobby2Presence &presence, SLNet::RakString userHandle) { unsigned int userIndex = GetUserIndexByUsername(userHandle); if (userIndex != static_cast(-1)) { presence=users[userIndex]->presence; } else { presence.status=Lobby2Presence::NOT_ONLINE; } } void Lobby2Server::SendUnifiedToMultiple( const SLNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char curOrderingChannel, const DataStructures::List systemAddresses ) { for (unsigned int i=0; i < systemAddresses.Size(); i++) SendUnified(bitStream,priority,reliability,curOrderingChannel,systemAddresses[i],false); }