diff options
Diffstat (limited to 'src/Server.cpp')
-rw-r--r-- | src/Server.cpp | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 000000000..5f0e007d7 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,707 @@ +// ReDucTor is an awesome guy who helped me a lot + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Server.h" +#include "ClientHandle.h" +#include "OSSupport/Timer.h" +#include "Mobs/Monster.h" +#include "OSSupport/Socket.h" +#include "Root.h" +#include "World.h" +#include "ChunkDef.h" +#include "PluginManager.h" +#include "GroupManager.h" +#include "ChatColor.h" +#include "Entities/Player.h" +#include "Inventory.h" +#include "Item.h" +#include "FurnaceRecipe.h" +#include "WebAdmin.h" +#include "Protocol/ProtocolRecognizer.h" +#include "CommandOutput.h" + +#include "MersenneTwister.h" + +#include "lib/iniFile/iniFile.h" +#include "Vector3f.h" + +#include <fstream> +#include <sstream> +#include <iostream> + +extern "C" { + #include "lib/zlib/zlib.h" +} + + + + +// For the "dumpmem" server command: +/// Synchronize this with main.cpp - the leak finder needs initialization before it can be used to dump memory +#define ENABLE_LEAK_FINDER + +#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) + #pragma warning(push) + #pragma warning(disable:4100) + #include "LeakFinder.h" + #pragma warning(pop) +#endif + + + + + +typedef std::list< cClientHandle* > ClientList; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer::cTickThread: + +cServer::cTickThread::cTickThread(cServer & a_Server) : + super("ServerTickThread"), + m_Server(a_Server) +{ +} + + + + + +void cServer::cTickThread::Execute(void) +{ + cTimer Timer; + + long long msPerTick = 50; + long long LastTime = Timer.GetNowTime(); + + while (!m_ShouldTerminate) + { + long long NowTime = Timer.GetNowTime(); + float DeltaTime = (float)(NowTime-LastTime); + m_ShouldTerminate = !m_Server.Tick(DeltaTime); + long long TickTime = Timer.GetNowTime() - NowTime; + + if (TickTime < msPerTick) + { + // Stretch tick time until it's at least msPerTick + cSleep::MilliSleep((unsigned int)(msPerTick - TickTime)); + } + + LastTime = NowTime; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer: + +cServer::cServer(void) : + m_ListenThreadIPv4(*this, cSocket::IPv4, "Client IPv4"), + m_ListenThreadIPv6(*this, cSocket::IPv6, "Client IPv6"), + m_bIsConnected(false), + m_bRestarting(false), + m_RCONServer(*this), + m_TickThread(*this) +{ +} + + + + + +void cServer::ClientDestroying(const cClientHandle * a_Client) +{ + m_SocketThreads.StopReading(a_Client); +} + + + + + +void cServer::NotifyClientWrite(const cClientHandle * a_Client) +{ + m_NotifyWriteThread.NotifyClientWrite(a_Client); +} + + + + + +void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Data) +{ + m_SocketThreads.Write(a_Client, a_Data); +} + + + + + +void cServer::QueueClientClose(const cClientHandle * a_Client) +{ + m_SocketThreads.QueueClose(a_Client); +} + + + + + +void cServer::RemoveClient(const cClientHandle * a_Client) +{ + m_SocketThreads.RemoveClient(a_Client); +} + + + + + +void cServer::ClientMovedToWorld(const cClientHandle * a_Client) +{ + cCSLock Lock(m_CSClients); + m_ClientsToRemove.push_back(const_cast<cClientHandle *>(a_Client)); +} + + + + + +void cServer::PlayerCreated(const cPlayer * a_Player) +{ + // To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread + cCSLock Lock(m_CSPlayerCountDiff); + m_PlayerCountDiff += 1; +} + + + + + +void cServer::PlayerDestroying(const cPlayer * a_Player) +{ + // To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread + cCSLock Lock(m_CSPlayerCountDiff); + m_PlayerCountDiff -= 1; +} + + + + + +bool cServer::InitServer(cIniFile & a_SettingsIni) +{ + m_Description = a_SettingsIni.GetValueSet("Server", "Description", "MCServer - in C++!").c_str(); + m_MaxPlayers = a_SettingsIni.GetValueSetI("Server", "MaxPlayers", 100); + m_bIsHardcore = a_SettingsIni.GetValueSetB("Server", "HardcoreEnabled", false); + m_PlayerCount = 0; + m_PlayerCountDiff = 0; + + if (m_bIsConnected) + { + LOGERROR("ERROR: Trying to initialize server while server is already running!"); + return false; + } + + LOGINFO("Compatible clients: %s", MCS_CLIENT_VERSIONS); + LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS); + + if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever + { + LOGERROR("WSAStartup() != 0"); + return false; + } + + bool HasAnyPorts = false; + AString Ports = a_SettingsIni.GetValueSet("Server", "Port", "25565"); + m_ListenThreadIPv4.SetReuseAddr(true); + if (m_ListenThreadIPv4.Initialize(Ports)) + { + HasAnyPorts = true; + } + + Ports = a_SettingsIni.GetValueSet("Server", "PortsIPv6", "25565"); + m_ListenThreadIPv6.SetReuseAddr(true); + if (m_ListenThreadIPv6.Initialize(Ports)) + { + HasAnyPorts = true; + } + + if (!HasAnyPorts) + { + LOGERROR("Couldn't open any ports. Aborting the server"); + return false; + } + + m_RCONServer.Initialize(a_SettingsIni); + + m_bIsConnected = true; + + m_ServerID = "-"; + if (a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true)) + { + MTRand mtrand1; + unsigned int r1 = (mtrand1.randInt() % 1147483647) + 1000000000; + unsigned int r2 = (mtrand1.randInt() % 1147483647) + 1000000000; + std::ostringstream sid; + sid << std::hex << r1; + sid << std::hex << r2; + m_ServerID = sid.str(); + m_ServerID.resize(16, '0'); + } + + m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE); + if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE) + { + m_ClientViewDistance = cClientHandle::MIN_VIEW_DISTANCE; + LOGINFO("Setting default viewdistance to the minimum of %d", m_ClientViewDistance); + } + if (m_ClientViewDistance > cClientHandle::MAX_VIEW_DISTANCE) + { + m_ClientViewDistance = cClientHandle::MAX_VIEW_DISTANCE; + LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance); + } + + m_NotifyWriteThread.Start(this); + + PrepareKeys(); + + return true; +} + + + + + +int cServer::GetNumPlayers(void) +{ + cCSLock Lock(m_CSPlayerCount); + return m_PlayerCount; +} + + + + + +void cServer::PrepareKeys(void) +{ + // TODO: Save and load key for persistence across sessions + // But generating the key takes only a moment, do we even need that? + + LOGD("Generating protocol encryption keypair..."); + + time_t CurTime = time(NULL); + CryptoPP::RandomPool rng; + rng.Put((const byte *)&CurTime, sizeof(CurTime)); + m_PrivateKey.GenerateRandomWithKeySize(rng, 1024); + CryptoPP::RSA::PublicKey pk(m_PrivateKey); + m_PublicKey = pk; +} + + + + + +void cServer::OnConnectionAccepted(cSocket & a_Socket) +{ + if (!a_Socket.IsValid()) + { + return; + } + + const AString & ClientIP = a_Socket.GetIPString(); + if (ClientIP.empty()) + { + LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting."); + a_Socket.CloseSocket(); + return; + } + + LOGD("Client \"%s\" connected!", ClientIP.c_str()); + + cClientHandle * NewHandle = new cClientHandle(&a_Socket, m_ClientViewDistance); + if (!m_SocketThreads.AddClient(a_Socket, NewHandle)) + { + // For some reason SocketThreads have rejected the handle, clean it up + LOGERROR("Client \"%s\" cannot be handled, server probably unstable", ClientIP.c_str()); + a_Socket.CloseSocket(); + delete NewHandle; + return; + } + + cCSLock Lock(m_CSClients); + m_Clients.push_back(NewHandle); +} + + + + + +bool cServer::Tick(float a_Dt) +{ + // Apply the queued playercount adjustments (postponed to avoid deadlocks) + int PlayerCountDiff = 0; + { + cCSLock Lock(m_CSPlayerCountDiff); + std::swap(PlayerCountDiff, m_PlayerCountDiff); + } + { + cCSLock Lock(m_CSPlayerCount); + m_PlayerCount += PlayerCountDiff; + } + + // Send the tick to the plugins, as well as let the plugin manager reload, if asked to (issue #102): + cPluginManager::Get()->Tick(a_Dt); + + // Let the Root process all the queued commands: + cRoot::Get()->TickCommands(); + + // Tick all clients not yet assigned to a world: + TickClients(a_Dt); + + if (!m_bRestarting) + { + return true; + } + else + { + m_bRestarting = false; + m_RestartEvent.Set(); + return false; + } +} + + + + + +void cServer::TickClients(float a_Dt) +{ + cClientHandleList RemoveClients; + { + cCSLock Lock(m_CSClients); + + // Remove clients that have moved to a world (the world will be ticking them from now on) + for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) + { + m_Clients.remove(*itr); + } // for itr - m_ClientsToRemove[] + m_ClientsToRemove.clear(); + + // Tick the remaining clients, take out those that have been destroyed into RemoveClients + for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) + { + if ((*itr)->IsDestroyed()) + { + // Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 ) + RemoveClients.push_back(*itr); + itr = m_Clients.erase(itr); + continue; + } + (*itr)->Tick(a_Dt); + ++itr; + } // for itr - m_Clients[] + } + + // Delete the clients that have been destroyed + for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) + { + delete *itr; + } // for itr - RemoveClients[] +} + + + + + +bool cServer::Start(void) +{ + if (!m_ListenThreadIPv4.Start()) + { + return false; + } + if (!m_ListenThreadIPv6.Start()) + { + return false; + } + if (!m_TickThread.Start()) + { + return false; + } + return true; +} + + + + + +bool cServer::Command(cClientHandle & a_Client, AString & a_Cmd) +{ + return cRoot::Get()->GetPluginManager()->CallHookChat(a_Client.GetPlayer(), a_Cmd); +} + + + + + +void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output) +{ + AStringVector split = StringSplit(a_Cmd, " "); + if (split.empty()) + { + return; + } + + // Special handling: "stop" and "restart" are built in + if ((split[0].compare("stop") == 0) || (split[0].compare("restart") == 0)) + { + return; + } + + // "help" and "reload" are to be handled by MCS, so that they work no matter what + if (split[0] == "help") + { + PrintHelp(split, a_Output); + return; + } + if (split[0] == "reload") + { + cPluginManager::Get()->ReloadPlugins(); + return; + } + + // There is currently no way a plugin can do these (and probably won't ever be): + if (split[0].compare("chunkstats") == 0) + { + cRoot::Get()->LogChunkStats(a_Output); + a_Output.Finished(); + return; + } + #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) + if (split[0].compare("dumpmem") == 0) + { + LeakFinderXmlOutput Output("memdump.xml"); + DumpUsedMemory(&Output); + return; + } + + if (split[0].compare("killmem") == 0) + { + while (true) + { + new char[100 * 1024 * 1024]; // Allocate and leak 100 MiB in a loop -> fill memory and kill MCS + } + } + #endif + + if (cPluginManager::Get()->ExecuteConsoleCommand(split, a_Output)) + { + a_Output.Finished(); + return; + } + + a_Output.Out("Unknown command, type 'help' for all commands."); + a_Output.Finished(); +} + + + + + +void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & a_Output) +{ + typedef std::pair<AString, AString> AStringPair; + typedef std::vector<AStringPair> AStringPairs; + + class cCallback : + public cPluginManager::cCommandEnumCallback + { + public: + cCallback(void) : m_MaxLen(0) {} + + virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override + { + if (!a_HelpString.empty()) + { + m_Commands.push_back(AStringPair(a_Command, a_HelpString)); + if (m_MaxLen < a_Command.length()) + { + m_MaxLen = a_Command.length(); + } + } + return false; + } + + AStringPairs m_Commands; + size_t m_MaxLen; + } Callback; + cPluginManager::Get()->ForEachConsoleCommand(Callback); + std::sort(Callback.m_Commands.begin(), Callback.m_Commands.end()); + for (AStringPairs::const_iterator itr = Callback.m_Commands.begin(), end = Callback.m_Commands.end(); itr != end; ++itr) + { + const AStringPair & cmd = *itr; + a_Output.Out(Printf("%-*s%s\n", Callback.m_MaxLen, cmd.first.c_str(), cmd.second.c_str())); + } // for itr - Callback.m_Commands[] + a_Output.Finished(); +} + + + + + +void cServer::BindBuiltInConsoleCommands(void) +{ + cPluginManager * PlgMgr = cPluginManager::Get(); + PlgMgr->BindConsoleCommand("help", NULL, " - Shows the available commands"); + PlgMgr->BindConsoleCommand("reload", NULL, " - Reloads all plugins"); + PlgMgr->BindConsoleCommand("restart", NULL, " - Restarts the server cleanly"); + PlgMgr->BindConsoleCommand("stop", NULL, " - Stops the server cleanly"); + PlgMgr->BindConsoleCommand("chunkstats", NULL, " - Displays detailed chunk memory statistics"); + #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) + PlgMgr->BindConsoleCommand("dumpmem", NULL, " - Dumps all used memory blocks together with their callstacks into memdump.xml"); + #endif +} + + + + + +void cServer::Shutdown(void) +{ + m_ListenThreadIPv4.Stop(); + m_ListenThreadIPv6.Stop(); + + m_bRestarting = true; + m_RestartEvent.Wait(); + + cRoot::Get()->SaveAllChunks(); + + cCSLock Lock(m_CSClients); + for( ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr ) + { + (*itr)->Destroy(); + delete *itr; + } + m_Clients.clear(); +} + + + + + +void cServer::KickUser(int a_ClientID, const AString & a_Reason) +{ + cCSLock Lock(m_CSClients); + for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + if ((*itr)->GetUniqueID() == a_ClientID) + { + (*itr)->Kick(a_Reason); + } + } // for itr - m_Clients[] +} + + + + + +void cServer::AuthenticateUser(int a_ClientID) +{ + cCSLock Lock(m_CSClients); + for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + if ((*itr)->GetUniqueID() == a_ClientID) + { + (*itr)->Authenticate(); + return; + } + } // for itr - m_Clients[] +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer::cNotifyWriteThread: + +cServer::cNotifyWriteThread::cNotifyWriteThread(void) : + super("ClientPacketThread"), + m_Server(NULL) +{ +} + + + + + +cServer::cNotifyWriteThread::~cNotifyWriteThread() +{ + m_ShouldTerminate = true; + m_Event.Set(); + Wait(); +} + + + + + +bool cServer::cNotifyWriteThread::Start(cServer * a_Server) +{ + m_Server = a_Server; + return super::Start(); +} + + + + + +void cServer::cNotifyWriteThread::Execute(void) +{ + cClientHandleList Clients; + while (!m_ShouldTerminate) + { + cCSLock Lock(m_CS); + while (m_Clients.size() == 0) + { + cCSUnlock Unlock(Lock); + m_Event.Wait(); + if (m_ShouldTerminate) + { + return; + } + } + + // Copy the clients to notify and unlock the CS: + Clients.splice(Clients.begin(), m_Clients); + Lock.Unlock(); + + for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr) + { + m_Server->m_SocketThreads.NotifyWrite(*itr); + } // for itr - Clients[] + Clients.clear(); + } // while (!mShouldTerminate) +} + + + + + +void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client) +{ + { + cCSLock Lock(m_CS); + m_Clients.remove(const_cast<cClientHandle *>(a_Client)); // Put it there only once + m_Clients.push_back(const_cast<cClientHandle *>(a_Client)); + } + m_Event.Set(); +} + + + + |