summaryrefslogblamecommitdiffstats
path: root/source/Server.cpp
blob: cc5e2a7316d6039abe9df02ac3ac5aba60817639 (plain) (tree)
1
2
3
4
5
6
7
8
9




                                                                                              

                         
                            
                         
                             

                  
                     








                          
                                        





                               



                   





                         
 













                                                                                                              






































































                                                                                                                  
                                                                                   
 
                                                





 
                                                              
 
                                             





 
                                                          
 
                                               





 
                                                  
 
                           



















                                                                                                
                                                                                                            
 
                                                                                      


















                                                                                                                              

                                                                       


                                                        
                                          












                                                                                                          

                                                     

                              




















                                                                                                                                 
         

                                                                                                   



                                        

                      









































                                                                       





                                                                            



                                                         








                                                          









                                                                                         

























                                                                                                  

                                                                                      
















































































































                                                                                                                                                                                        
                                                                      
 
                                                                                           





 
                                                  
 

                                                      



                       
                                            














                                                                                 
                                                                                  


                       
                                              
         
                                              








                                                                                                           
                                          




                                                                      
                                                                                                                                  





                                                    
                                               





                                                                                  
                                              


                       
                                              




                                                








                                                                               


















                                                                                                                
                             
         
                                                 
                 



                                                                                     










                                                                   
                                                                                                                     
 
                                              
         




                                                                     


                       
                                                                                          















                                                                                          
                                  
































































































































                                                                                                                       

// 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 "Player.h"
#include "Inventory.h"
#include "Item.h"
#include "FurnaceRecipe.h"
#include "Tracer.h"
#include "WebAdmin.h"
#include "Protocol/ProtocolRecognizer.h"

#include "MersenneTwister.h"

#include "../iniFile/iniFile.h"
#include "Vector3f.h"

#include <fstream>
#include <sstream>
#include <iostream>

extern "C" {
	#include "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;





struct cServer::sServerState
{
	sServerState()
		: pListenThread( 0 )
		, pTickThread( 0 )
		, bStopListenThread( false )
		, bStopTickThread( false )
	{}
	cSocket SListenClient; // socket listening for client calls

	cThread* pListenThread;	bool bStopListenThread;
	cThread* pTickThread;	bool bStopTickThread;

	cEvent RestartEvent;
	std::string ServerID;
};





cServer * cServer::GetServer()
{
	LOGWARN("WARNING: Using deprecated function cServer::GetServer() use cRoot::Get()->GetServer() instead!");
	return cRoot::Get()->GetServer();
}





void cServer::ServerListenThread( void *a_Args )
{
	LOG("ServerListenThread");
	cServer* self = (cServer*)a_Args;
	sServerState* m_pState = self->m_pState;
	while( !m_pState->bStopListenThread )
	{
		self->StartListenClient();
	}
}





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





bool cServer::InitServer(cIniFile & a_SettingsIni)
{
	if (m_bIsConnected)
	{
		LOGERROR("ERROR: Trying to initialize server while server is already running!");
		return false;
	}

	printf("/============================\\\n");
	printf("|   Custom Minecraft Server  |\n");
	printf("|  Created by Kevin Bansberg |\n");
	printf("|       A.K.A. FakeTruth     |\n");
	printf("| Monsters by Alex Sonek     |\n");
	printf("|       A.K.A. Duralex       |\n");
	printf("| Stuff by Mattes D          |\n");
	printf("|       A.K.A. _Xoft(o)      |\n");
	printf("\\============================/\n");
	printf("More info: WWW.MC-SERVER.ORG\n");
	printf("           WWW.AE-C.NET\n");
	printf("           WWW.RBTHINKTANK.COM\n");
	printf("email: faketruth@gmail.com\n\n");

	LOG("Starting up server.");
	LOGINFO("Compatible clients: %s, protocol versions %s", MCS_CLIENT_VERSIONS, MCS_PROTOCOL_VERSIONS);

	if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever
	{
		LOGERROR("WSAStartup() != 0");
		return false;
	}

	m_pState->SListenClient = cSocket::CreateSocket();

	if( !m_pState->SListenClient.IsValid() )
	{
		LOGERROR("m_SListenClient==INVALID_SOCKET (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() );
		return false;
	}

	if( m_pState->SListenClient.SetReuseAddress() == -1 )
	{
		LOGERROR("setsockopt == -1");
		return false;
	}

	int Port = a_SettingsIni.GetValueSetI("Server", "Port", 25565);

	cSocket::SockAddr_In local;
	local.Family = cSocket::ADDRESS_FAMILY_INTERNET;
	local.Address = cSocket::INTERNET_ADDRESS_ANY;
	local.Port = (unsigned short)Port;

	if( m_pState->SListenClient.Bind( local ) != 0 )
	{
		LOGERROR("bind fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() );
		return false;
	}
	
	if( m_pState->SListenClient.Listen( 10 ) != 0)
	{
		LOGERROR("listen fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() );
		return false;
	}

	m_iServerPort = Port;
	LOG("Port %i has been bound", m_iServerPort);
	m_bIsConnected = true;

	m_pState->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;
		std::string ServerID = sid.str();
		ServerID.resize(16, '0');
		m_pState->ServerID = ServerID;
	}
	
	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;
}





cServer::cServer()
	: m_pState( new sServerState )
	, m_Millisecondsf( 0 )
	, m_Milliseconds( 0 )
	, m_bIsConnected( false )
	, m_iServerPort( 0 )
	, m_bRestarting( false )
{
}





cServer::~cServer()
{
	// TODO: Shut down the server gracefully
	if ( m_pState->SListenClient )
	{
		m_pState->SListenClient.CloseSocket();
	}
	m_pState->SListenClient = 0;

	m_pState->bStopListenThread = true;
	delete m_pState->pListenThread;	m_pState->pListenThread = NULL;
	m_pState->bStopTickThread = true;
	delete m_pState->pTickThread;	m_pState->pTickThread = NULL;

	delete m_pState;
}





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?
	
	LOG("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::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude)
{
	cCSLock Lock(m_CSClients);
	for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
	{
		if ((*itr == a_Exclude) || !(*itr)->IsLoggedIn())
		{
			continue;
		}
		(*itr)->SendChat(a_Message);
	}
}





void cServer::StartListenClient()
{
	cSocket SClient = m_pState->SListenClient.Accept();

	if (!SClient.IsValid())
	{
		return;
	}
	
	const AString & ClientIP = SClient.GetIPString();
	if (ClientIP.empty())
	{
		LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting.");
		SClient.CloseSocket();
		return;
	}

	LOG("Client \"%s\" connected!", ClientIP.c_str());

	cClientHandle * NewHandle = new cClientHandle(&SClient, m_ClientViewDistance);
	if (!m_SocketThreads.AddClient(SClient, NewHandle))
	{
		// For some reason SocketThreads have rejected the handle, clean it up
		LOGERROR("Client \"%s\" cannot be handled, server probably unstable", SClient.GetIPString().c_str());
		SClient.CloseSocket();
		delete NewHandle;
		return;
	}
	
	cCSLock Lock(m_CSClients);
	m_Clients.push_back( NewHandle );
}





bool cServer::Tick(float a_Dt)
{
    //LOG("1. Tick %0.2f", a_Dt);
	if( a_Dt > 100.f ) a_Dt = 100.f; // Don't go over 1/10 second

	m_Millisecondsf += a_Dt;
	if( m_Millisecondsf > 1.f )
	{
		m_Milliseconds += (int)m_Millisecondsf;
		m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf;
	}

	cRoot::Get()->TickWorlds( a_Dt ); // TODO - Maybe give all worlds their own thread?

	cClientHandleList RemoveClients;
	{
		cCSLock Lock(m_CSClients);
		for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
		{
			if ((*itr)->IsDestroyed())
			{
				RemoveClients.push_back(*itr);  // Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 )
				itr = m_Clients.erase(itr);
				continue;
			}
			(*itr)->Tick(a_Dt);
			++itr;
		}  // for itr - m_Clients[]
	}
	for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
	{
		delete *itr;
	} // for itr - RemoveClients[]

	cRoot::Get()->GetPluginManager()->Tick( a_Dt );

	if( !m_bRestarting )
	{
		return true;
	}
	else
	{
		m_bRestarting = false;
		m_pState->RestartEvent.Set();
		return false;
	}
}





void ServerTickThread( void * a_Param )
{
	LOG("ServerTickThread");
	cServer *CServerObj = (cServer*)a_Param;

	cTimer Timer;

	long long msPerTick = 50;	// TODO - Put this in server config file
	long long LastTime = Timer.GetNowTime();

	bool bKeepGoing = true;
	while( bKeepGoing )
	{
		long long NowTime = Timer.GetNowTime();
		float DeltaTime = (float)(NowTime-LastTime);
		bKeepGoing = CServerObj->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;
	}

	LOG("TICK THREAD STOPPED");
}





void cServer::StartListenThread()
{
	m_pState->pListenThread = new cThread( ServerListenThread, this, "cServer::ServerListenThread" );
	m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" );
	m_pState->pListenThread->Start( true );
	m_pState->pTickThread->Start( true );
}





bool cServer::Command(cClientHandle & a_Client, const AString & a_Cmd)
{
	return cRoot::Get()->GetPluginManager()->CallHookChat(a_Client.GetPlayer(), a_Cmd);
}





void cServer::ServerCommand(const AString & a_Cmd)
{
	AStringVector split = StringSplit(a_Cmd, " ");
	if (split.empty())
	{
		return;
	}
	
	if (split[0].compare( "help" ) == 0)
	{
		printf("================== ALL COMMANDS ===================\n");
		printf("help       - Shows this message\n");
		printf("save-all   - Saves all loaded chunks to disk\n");
		printf("list       - Lists all players currently in server\n");
		printf("unload     - Unloads all unused chunks\n");
		printf("numchunks  - Shows number of chunks currently loaded\n");
		printf("chunkstats - Shows chunks statistics\n");
		printf("say        - Sends a chat message to all players\n");
		printf("restart    - Kicks all clients, and saves everything\n");
		printf("            and clears memory\n");
		printf("stop       - Saves everything and closes server\n");
		printf("===================================================\n");
		return;
	}
	if ((split[0].compare("stop") == 0) || (split[0].compare("restart") == 0))
	{
		return;
	}
	if (split[0].compare("save-all") == 0)
	{
		cRoot::Get()->SaveAllChunks();
		return;
	}
	if (split[0].compare("unload") == 0)
	{
		LOG("Num loaded chunks before: %i", cRoot::Get()->GetTotalChunkCount() );
		cRoot::Get()->GetDefaultWorld()->UnloadUnusedChunks();  // TODO: Iterate through ALL worlds
		LOG("Num loaded chunks after: %i", cRoot::Get()->GetTotalChunkCount() );
		return;
	}
	if (split[0].compare("list") == 0)
	{
		class cPlayerLogger : public cPlayerListCallback
		{
			virtual bool Item(cPlayer * a_Player) override
			{
				LOG("\t%s @ %s", a_Player->GetName().c_str(), a_Player->GetClientHandle()->GetIPString().c_str());
				return false;
			}
		} Logger;
		cRoot::Get()->ForEachPlayer(Logger);
		return;
	}
	if (split[0].compare("numchunks") == 0)
	{
		LOG("Num loaded chunks: %i", cRoot::Get()->GetTotalChunkCount() );
		return;
	}
	if (split[0].compare("chunkstats") == 0)
	{
		cRoot::Get()->LogChunkStats();
		return;
	}
	
	if (split[0].compare("monsters") == 0)
	{
		// TODO: cWorld::ListMonsters();
		return;
	}
	
	#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
	if (split[0].compare("dumpmem") == 0)
	{
		LeakFinderXmlOutput Output("memdump.xml");
		DumpUsedMemory(&Output);
		return;
	}
	#endif
	
	if (split[0].compare("primaryserverversion") == 0)
	{
		if (split.size() > 1)
		{
			int Version = atol(split[1].c_str());
			if (Version == 0)
			{
				LOGWARNING("Cannot parse version \"%s\". Not setting anything.");
				return;
			}
			cRoot::Get()->SetPrimaryServerVersion(Version);
		}
		LOGINFO("Primary server version: %d (%s)", 
			cRoot::Get()->m_PrimaryServerVersion, 
			cProtocolRecognizer::GetVersionTextFromInt(cRoot::Get()->m_PrimaryServerVersion).c_str()
		);
		return;
	}
	
	if (split.size() > 1)
	{
		if (split[0].compare("say") == 0)
		{
			AString ToSay = a_Cmd.substr(a_Cmd.find_first_of("say") + 4);
			AString Message = cChatColor::Purple + "[SERVER] " + ToSay;
			LOG("[SERVER]: %s", ToSay.c_str());
			BroadcastChat(Message);
			return;
		}
	}
	printf("Unknown command, type 'help' for all commands.\n");
	// LOG("You didn't enter anything? (%s)", a_Cmd.c_str() );
}





void cServer::SendMessage(const AString & a_Message, cPlayer * a_Player /* = NULL */, bool a_bExclude /* = false */ )
{
	if ((a_Player != NULL) && !a_bExclude)
	{
		cClientHandle * Client = a_Player->GetClientHandle();
		if (Client != NULL)
		{
			Client->SendChat(a_Message);
		}
		return;
	}

	BroadcastChat(a_Message, (a_Player != NULL) ? a_Player->GetClientHandle() : NULL);
}





void cServer::Shutdown()
{
	m_bRestarting = true;
	m_pState->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();
}





const AString & cServer::GetServerID(void) const
{
	return m_pState->ServerID;
}





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();
		}
	}  // for itr - m_Clients[]
}





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cServer::cClientPacketThread:

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