about summary refs log tree commit diff
diff options
context:
space:
mode:
authoroy <Tom_Adams@web.de>2011-03-15 09:58:57 +0100
committeroy <Tom_Adams@web.de>2011-03-15 09:58:57 +0100
commit27e5a6af0d09192e1a865332f35bde4a0ac180dd (patch)
tree65027504bc580101b39605714cc801dcb5b8e000
parent7b91ebd01cc0a0f466518144e82778fe95d3aed8 (diff)
downloadzcatch-27e5a6af0d09192e1a865332f35bde4a0ac180dd.tar.gz
zcatch-27e5a6af0d09192e1a865332f35bde4a0ac180dd.zip
made clients sending startinfo a requirement to prevent empty player infos
-rw-r--r--src/engine/client/client.cpp10
-rw-r--r--src/engine/server.h2
-rw-r--r--src/engine/server/server.cpp385
-rw-r--r--src/engine/server/server.h1
-rw-r--r--src/engine/shared/protocol.h1
-rw-r--r--src/game/server/gamecontext.cpp83
-rw-r--r--src/game/server/gamecontext.h2
-rw-r--r--src/game/server/player.h2
8 files changed, 253 insertions, 233 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 5ed0b37b..1c0a3a01 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -518,8 +518,6 @@ void CClient::SendInfo()
 {
 	CMsgPacker Msg(NETMSG_INFO);
 	Msg.AddString(GameClient()->NetVersion(), 128);
-	Msg.AddString(g_Config.m_PlayerName, 128);
-	Msg.AddString(g_Config.m_ClanName, 128);
 	Msg.AddString(g_Config.m_Password, 128);
 	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
 }
@@ -1175,7 +1173,6 @@ void CClient::ProcessPacket(CNetChunk *pPacket)
 					{
 						m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
 						SendReady();
-						GameClient()->OnConnected();
 					}
 					else
 					{
@@ -1238,8 +1235,7 @@ void CClient::ProcessPacket(CNetChunk *pPacket)
 					if(!pError)
 					{
 						m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
-						SendReady();
-						GameClient()->OnConnected();
+						SendReady();						
 					}
 					else
 						DisconnectWithReason(pError);
@@ -1261,6 +1257,10 @@ void CClient::ProcessPacket(CNetChunk *pPacket)
 					}
 				}
 			}
+			else if(Msg == NETMSG_CON_READY)
+			{
+				GameClient()->OnConnected();
+			}
 			else if(Msg == NETMSG_PING)
 			{
 				CMsgPacker Msg(NETMSG_PING_REPLY);
diff --git a/src/engine/server.h b/src/engine/server.h
index 18d92dfe..4f37e8fe 100644
--- a/src/engine/server.h
+++ b/src/engine/server.h
@@ -76,6 +76,8 @@ public:
 	virtual void OnClientDrop(int ClientID) = 0;
 	virtual void OnClientDirectInput(int ClientID, void *pInput) = 0;
 	virtual void OnClientPredictedInput(int ClientID, void *pInput) = 0;
+
+	virtual bool IsClientReady(int ClientID) = 0;
 	
 	virtual const char *GameType() = 0;
 	virtual const char *Version() = 0;
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 39e63b42..f385a312 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -572,6 +572,12 @@ void CServer::SendMap(int ClientID)
 	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
 }
 
+void CServer::SendConnectionReady(int ClientID)
+{
+	CMsgPacker Msg(NETMSG_CON_READY);
+	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
+}
+
 void CServer::SendRconLine(int ClientID, const char *pLine)
 {
 	CMsgPacker Msg(NETMSG_RCON_LINE);
@@ -612,251 +618,244 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 	if(Unpacker.Error())
 		return;
 	
-	if(m_aClients[ClientID].m_State == CClient::STATE_AUTH)
+	if(Sys)
 	{
-		if(Sys && Msg == NETMSG_INFO)
+		// system message
+		if(Msg == NETMSG_INFO)
 		{
-			char aVersion[64];
-			const char *pPassword;
-			str_copy(aVersion, Unpacker.GetString(CUnpacker::SANITIZE_CC), 64);
-			if(str_comp(aVersion, GameServer()->NetVersion()) != 0)
+			if(m_aClients[ClientID].m_State == CClient::STATE_AUTH)
 			{
-				// OH FUCK! wrong version, drop him
-				char aReason[256];
-				str_format(aReason, sizeof(aReason), "Wrong version. Server is running '%s' and client '%s'", GameServer()->NetVersion(), aVersion);
-				m_NetServer.Drop(ClientID, aReason);
-				return;
-			}
+				const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+				if(str_comp(pVersion, GameServer()->NetVersion()) != 0)
+				{
+					// wrong version
+					char aReason[256];
+					str_format(aReason, sizeof(aReason), "Wrong version. Server is running '%s' and client '%s'", GameServer()->NetVersion(), pVersion);
+					m_NetServer.Drop(ClientID, aReason);
+					return;
+				}
 			
-			str_copy(m_aClients[ClientID].m_aName, Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), MAX_NAME_LENGTH);
-			str_copy(m_aClients[ClientID].m_aClan, Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), MAX_CLANNAME_LENGTH);
-			pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+				const char *pPassword = Unpacker.GetString(CUnpacker::SANITIZE_CC);
+				if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0)
+				{
+					// wrong password
+					m_NetServer.Drop(ClientID, "Wrong password");
+					return;
+				}
 			
-			if(g_Config.m_Password[0] != 0 && str_comp(g_Config.m_Password, pPassword) != 0)
-			{
-				// wrong password
-				m_NetServer.Drop(ClientID, "Wrong password");
-				return;
+				m_aClients[ClientID].m_State = CClient::STATE_CONNECTING;
+				SendMap(ClientID);
 			}
-			
-			m_aClients[ClientID].m_State = CClient::STATE_CONNECTING;
-			SendMap(ClientID);
 		}
-	}
-	else
-	{
-		if(Sys)
+		else if(Msg == NETMSG_REQUEST_MAP_DATA)
 		{
-			// system message
-			if(Msg == NETMSG_REQUEST_MAP_DATA)
-			{
-				int Chunk = Unpacker.GetInt();
-				int ChunkSize = 1024-128;
-				int Offset = Chunk * ChunkSize;
-				int Last = 0;
+			int Chunk = Unpacker.GetInt();
+			int ChunkSize = 1024-128;
+			int Offset = Chunk * ChunkSize;
+			int Last = 0;
 				
-				// drop faulty map data requests
-				if(Chunk < 0 || Offset > m_CurrentMapSize)
-					return;
+			// drop faulty map data requests
+			if(Chunk < 0 || Offset > m_CurrentMapSize)
+				return;
 				
-				if(Offset+ChunkSize >= m_CurrentMapSize)
-				{
-					ChunkSize = m_CurrentMapSize-Offset;
-					if(ChunkSize < 0)
-						ChunkSize = 0;
-					Last = 1;
-				}
+			if(Offset+ChunkSize >= m_CurrentMapSize)
+			{
+				ChunkSize = m_CurrentMapSize-Offset;
+				if(ChunkSize < 0)
+					ChunkSize = 0;
+				Last = 1;
+			}
 				
-				CMsgPacker Msg(NETMSG_MAP_DATA);
-				Msg.AddInt(Last);
-				Msg.AddInt(m_CurrentMapCrc);
-				Msg.AddInt(Chunk);
-				Msg.AddInt(ChunkSize);
-				Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize);
-				SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
+			CMsgPacker Msg(NETMSG_MAP_DATA);
+			Msg.AddInt(Last);
+			Msg.AddInt(m_CurrentMapCrc);
+			Msg.AddInt(Chunk);
+			Msg.AddInt(ChunkSize);
+			Msg.AddRaw(&m_pCurrentMapData[Offset], ChunkSize);
+			SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
 				
-				if(g_Config.m_Debug)
-				{
-					char aBuf[256];
-					str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize);
-					Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf);
-				}
+			if(g_Config.m_Debug)
+			{
+				char aBuf[256];
+				str_format(aBuf, sizeof(aBuf), "sending chunk %d with size %d", Chunk, ChunkSize);
+				Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf);
 			}
-			else if(Msg == NETMSG_READY)
+		}
+		else if(Msg == NETMSG_READY)
+		{
+			if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTING)
 			{
-				if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTING)
-				{
-					Addr = m_NetServer.ClientAddr(ClientID);
+				Addr = m_NetServer.ClientAddr(ClientID);
 					
-					char aBuf[256];
-					str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x ip=%d.%d.%d.%d",
-						ClientID, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]);
-					Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
-					m_aClients[ClientID].m_State = CClient::STATE_READY;
-					GameServer()->OnClientConnected(ClientID);
-				}
+				char aBuf[256];
+				str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x ip=%d.%d.%d.%d",
+					ClientID, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]);
+				Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
+				m_aClients[ClientID].m_State = CClient::STATE_READY;
+				GameServer()->OnClientConnected(ClientID);
+				SendConnectionReady(ClientID);
 			}
-			else if(Msg == NETMSG_ENTERGAME)
+		}
+		else if(Msg == NETMSG_ENTERGAME)
+		{
+			if(m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID))
 			{
-				if(m_aClients[ClientID].m_State == CClient::STATE_READY)
-				{
-					Addr = m_NetServer.ClientAddr(ClientID);
+				Addr = m_NetServer.ClientAddr(ClientID);
 					
-					char aBuf[256];
-					str_format(aBuf, sizeof(aBuf), "player has entered the game. ClientID=%x ip=%d.%d.%d.%d",
-						ClientID, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]);
-					Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-					m_aClients[ClientID].m_State = CClient::STATE_INGAME;
-					GameServer()->OnClientEnter(ClientID);
-				}
+				char aBuf[256];
+				str_format(aBuf, sizeof(aBuf), "player has entered the game. ClientID=%x ip=%d.%d.%d.%d",
+					ClientID, Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3]);
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+				m_aClients[ClientID].m_State = CClient::STATE_INGAME;
+				GameServer()->OnClientEnter(ClientID);
 			}
-			else if(Msg == NETMSG_INPUT)
-			{
-				CClient::CInput *pInput;
-				int64 TagTime;
+		}
+		else if(Msg == NETMSG_INPUT)
+		{
+			CClient::CInput *pInput;
+			int64 TagTime;
 				
-				m_aClients[ClientID].m_LastAckedSnapshot = Unpacker.GetInt();
-				int IntendedTick = Unpacker.GetInt();
-				int Size = Unpacker.GetInt();
+			m_aClients[ClientID].m_LastAckedSnapshot = Unpacker.GetInt();
+			int IntendedTick = Unpacker.GetInt();
+			int Size = Unpacker.GetInt();
 				
-				// check for errors
-				if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE)
-					return;
+			// check for errors
+			if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE)
+				return;
 
-				if(m_aClients[ClientID].m_LastAckedSnapshot > 0)
-					m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL;
+			if(m_aClients[ClientID].m_LastAckedSnapshot > 0)
+				m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL;
 					
-				if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0)
-					m_aClients[ClientID].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq());
+			if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0)
+				m_aClients[ClientID].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq());
 
-				// add message to report the input timing
-				// skip packets that are old
-				if(IntendedTick > m_aClients[ClientID].m_LastInputTick)
-				{
-					int TimeLeft = ((TickStartTime(IntendedTick)-time_get())*1000) / time_freq();
+			// add message to report the input timing
+			// skip packets that are old
+			if(IntendedTick > m_aClients[ClientID].m_LastInputTick)
+			{
+				int TimeLeft = ((TickStartTime(IntendedTick)-time_get())*1000) / time_freq();
 					
-					CMsgPacker Msg(NETMSG_INPUTTIMING);
-					Msg.AddInt(IntendedTick);
-					Msg.AddInt(TimeLeft);
-					SendMsgEx(&Msg, 0, ClientID, true);
-				}
+				CMsgPacker Msg(NETMSG_INPUTTIMING);
+				Msg.AddInt(IntendedTick);
+				Msg.AddInt(TimeLeft);
+				SendMsgEx(&Msg, 0, ClientID, true);
+			}
 
-				m_aClients[ClientID].m_LastInputTick = IntendedTick;
+			m_aClients[ClientID].m_LastInputTick = IntendedTick;
 
-				pInput = &m_aClients[ClientID].m_aInputs[m_aClients[ClientID].m_CurrentInput];
+			pInput = &m_aClients[ClientID].m_aInputs[m_aClients[ClientID].m_CurrentInput];
 				
-				if(IntendedTick <= Tick())
-					IntendedTick = Tick()+1;
+			if(IntendedTick <= Tick())
+				IntendedTick = Tick()+1;
 
-				pInput->m_GameTick = IntendedTick;
+			pInput->m_GameTick = IntendedTick;
 				
-				for(int i = 0; i < Size/4; i++)
-					pInput->m_aData[i] = Unpacker.GetInt();
+			for(int i = 0; i < Size/4; i++)
+				pInput->m_aData[i] = Unpacker.GetInt();
 				
-				mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int));
+			mem_copy(m_aClients[ClientID].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE*sizeof(int));
 				
-				m_aClients[ClientID].m_CurrentInput++;
-				m_aClients[ClientID].m_CurrentInput %= 200;
+			m_aClients[ClientID].m_CurrentInput++;
+			m_aClients[ClientID].m_CurrentInput %= 200;
 			
-				// call the mod with the fresh input data
-				if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
-					GameServer()->OnClientDirectInput(ClientID, m_aClients[ClientID].m_LatestInput.m_aData);
-			}
-			else if(Msg == NETMSG_RCON_CMD)
+			// call the mod with the fresh input data
+			if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
+				GameServer()->OnClientDirectInput(ClientID, m_aClients[ClientID].m_LatestInput.m_aData);
+		}
+		else if(Msg == NETMSG_RCON_CMD)
+		{
+			const char *pCmd = Unpacker.GetString();
+				
+			if(Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed)
 			{
-				const char *pCmd = Unpacker.GetString();
+				char aBuf[256];
+				str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd);
+				Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
+				m_RconClientID = ClientID;
+				Console()->ExecuteLine(pCmd);
+				m_RconClientID = -1;
+			}
+		}
+		else if(Msg == NETMSG_RCON_AUTH)
+		{
+			const char *pPw;
+			Unpacker.GetString(); // login name, not used
+			pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC);
 				
-				if(Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed)
+			if(Unpacker.Error() == 0)
+			{
+				if(g_Config.m_SvRconPassword[0] == 0)
 				{
-					char aBuf[256];
-					str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd);
-					Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
-					m_RconClientID = ClientID;
-					Console()->ExecuteLine(pCmd);
-					m_RconClientID = -1;
+					SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password to enable the remote console.");
 				}
-			}
-			else if(Msg == NETMSG_RCON_AUTH)
-			{
-				const char *pPw;
-				Unpacker.GetString(); // login name, not used
-				pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC);
-				
-				if(Unpacker.Error() == 0)
+				else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0)
 				{
-					if(g_Config.m_SvRconPassword[0] == 0)
-					{
-						SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password to enable the remote console.");
-					}
-					else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0)
-					{
-						CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
-						Msg.AddInt(1);
-						SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
+					CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
+					Msg.AddInt(1);
+					SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
 						
-						m_aClients[ClientID].m_Authed = 1;
-						SendRconLine(ClientID, "Authentication successful. Remote console access granted.");
-						char aBuf[256];
-						str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID);
-						Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-					}
-					else if(g_Config.m_SvRconMaxTries)
+					m_aClients[ClientID].m_Authed = 1;
+					SendRconLine(ClientID, "Authentication successful. Remote console access granted.");
+					char aBuf[256];
+					str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID);
+					Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+				}
+				else if(g_Config.m_SvRconMaxTries)
+				{
+					m_aClients[ClientID].m_AuthTries++;
+					char aBuf[128];
+					str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, g_Config.m_SvRconMaxTries);
+					SendRconLine(ClientID, aBuf);
+					if(m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries)
 					{
-						m_aClients[ClientID].m_AuthTries++;
-						char aBuf[128];
-						str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, g_Config.m_SvRconMaxTries);
-						SendRconLine(ClientID, aBuf);
-						if(m_aClients[ClientID].m_AuthTries >= g_Config.m_SvRconMaxTries)
+						if(!g_Config.m_SvRconBantime)
+							m_NetServer.Drop(ClientID, "Too many remote console authentication tries");
+						else
 						{
-							if(!g_Config.m_SvRconBantime)
-								m_NetServer.Drop(ClientID, "Too many remote console authentication tries");
-							else
-							{
-								NETADDR Addr = m_NetServer.ClientAddr(ClientID);
-								BanAdd(Addr, g_Config.m_SvRconBantime*60, "Too many remote console authentication tries");
-							}
+							NETADDR Addr = m_NetServer.ClientAddr(ClientID);
+							BanAdd(Addr, g_Config.m_SvRconBantime*60, "Too many remote console authentication tries");
 						}
 					}
-					else
-					{
-						SendRconLine(ClientID, "Wrong password.");
-					}
 				}
-			}
-			else if(Msg == NETMSG_PING)
-			{
-				CMsgPacker Msg(NETMSG_PING_REPLY);
-				SendMsgEx(&Msg, 0, ClientID, true);
-			}
-			else
-			{
-				if(g_Config.m_Debug)
+				else
 				{
-					char aHex[] = "0123456789ABCDEF";
-					char aBuf[512];
-
-					for(int b = 0; b < pPacket->m_DataSize && b < 32; b++)
-					{
-						aBuf[b*3] = aHex[((const unsigned char *)pPacket->m_pData)[b]>>4];
-						aBuf[b*3+1] = aHex[((const unsigned char *)pPacket->m_pData)[b]&0xf];
-						aBuf[b*3+2] = ' ';
-						aBuf[b*3+3] = 0;
-					}
-
-					char aBufMsg[256];
-					str_format(aBufMsg, sizeof(aBufMsg), "strange message ClientID=%d msg=%d data_size=%d", ClientID, Msg, pPacket->m_DataSize);
-					Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBufMsg);
-					Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf);
+					SendRconLine(ClientID, "Wrong password.");
 				}
 			}
 		}
+		else if(Msg == NETMSG_PING)
+		{
+			CMsgPacker Msg(NETMSG_PING_REPLY);
+			SendMsgEx(&Msg, 0, ClientID, true);
+		}
 		else
 		{
-			// game message
-			if(m_aClients[ClientID].m_State >= CClient::STATE_READY)
-				GameServer()->OnMessage(Msg, &Unpacker, ClientID);
+			if(g_Config.m_Debug)
+			{
+				char aHex[] = "0123456789ABCDEF";
+				char aBuf[512];
+
+				for(int b = 0; b < pPacket->m_DataSize && b < 32; b++)
+				{
+					aBuf[b*3] = aHex[((const unsigned char *)pPacket->m_pData)[b]>>4];
+					aBuf[b*3+1] = aHex[((const unsigned char *)pPacket->m_pData)[b]&0xf];
+					aBuf[b*3+2] = ' ';
+					aBuf[b*3+3] = 0;
+				}
+
+				char aBufMsg[256];
+				str_format(aBufMsg, sizeof(aBufMsg), "strange message ClientID=%d msg=%d data_size=%d", ClientID, Msg, pPacket->m_DataSize);
+				Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBufMsg);
+				Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf);
+			}
 		}
 	}
+	else
+	{
+		// game message
+		if(m_aClients[ClientID].m_State >= CClient::STATE_READY)
+			GameServer()->OnMessage(Msg, &Unpacker, ClientID);
+	}
 }
 	
 void CServer::SendServerInfo(NETADDR *pAddr, int Token)
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index 74b524a8..77cfc484 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -153,6 +153,7 @@ public:
 	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
 
 	void SendMap(int ClientID);
+	void SendConnectionReady(int ClientID);
 	void SendRconLine(int ClientID, const char *pLine);
 	static void SendRconLineAuthed(const char *pLine, void *pUser);
 	
diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h
index b714b328..296294d9 100644
--- a/src/engine/shared/protocol.h
+++ b/src/engine/shared/protocol.h
@@ -39,6 +39,7 @@ enum
 	// sent by server
 	NETMSG_MAP_CHANGE,		// sent when client should switch map
 	NETMSG_MAP_DATA,		// map transfer, contains a chunk of the map file
+	NETMSG_CON_READY,		// connection is ready, client should send start info
 	NETMSG_SNAP,			// normal snapshot, multiple parts
 	NETMSG_SNAPEMPTY,		// empty snapshot
 	NETMSG_SNAPSINGLE,		// ?
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 7f8a075f..8c3dc3e3 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -786,57 +786,65 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 		else
 			pPlayer->m_SpectatorID = pMsg->m_SpectatorID;
 	}
-	else if (MsgID == NETMSGTYPE_CL_CHANGEINFO || MsgID == NETMSGTYPE_CL_STARTINFO)
-	{
-		CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg;
-		
-		if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo+Server()->TickSpeed()*5 > Server()->Tick())
+	else if (MsgID == NETMSGTYPE_CL_STARTINFO)
+	{		
+		if(pPlayer->m_IsReady)
 			return;
-			
+
+		CNetMsg_Cl_StartInfo *pMsg = (CNetMsg_Cl_StartInfo *)pRawMsg;	
 		pPlayer->m_LastChangeInfo = Server()->Tick();
 		
+		// set start infos
+		Server()->SetClientName(ClientID, pMsg->m_pName);
+		str_copy(pPlayer->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_SkinName));
 		pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor;
 		pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody;
 		pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet;
+		m_pController->OnPlayerInfoChange(pPlayer);
 
-		// copy old name
-		char aOldName[MAX_NAME_LENGTH];
-		str_copy(aOldName, Server()->ClientName(ClientID), MAX_NAME_LENGTH);
+		// send vote options
+		CNetMsg_Sv_VoteClearOptions ClearMsg;
+		Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientID);
+		CVoteOption *pCurrent = m_pVoteOptionFirst;
+		while(pCurrent)
+		{
+			CNetMsg_Sv_VoteOption OptionMsg;
+			OptionMsg.m_pCommand = pCurrent->m_aCommand;
+			Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, ClientID);
+			pCurrent = pCurrent->m_pNext;
+		}
+			
+		// send tuning parameters to client
+		SendTuningParams(ClientID);
+
+		// client is ready to enter
+		pPlayer->m_IsReady = true;
+		CNetMsg_Sv_ReadyToEnter m;
+		Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID);
+	}
+	else if (MsgID == NETMSGTYPE_CL_CHANGEINFO)
+	{	
+		if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo+Server()->TickSpeed()*5 > Server()->Tick())
+			return;
+		
+		CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg;
+		pPlayer->m_LastChangeInfo = Server()->Tick();
 		
+		// set infos
+		char aOldName[MAX_NAME_LENGTH];
+		str_copy(aOldName, Server()->ClientName(ClientID), sizeof(aOldName));	
 		Server()->SetClientName(ClientID, pMsg->m_pName);
-		if(MsgID == NETMSGTYPE_CL_CHANGEINFO && str_comp(aOldName, Server()->ClientName(ClientID)) != 0)
+		if(str_comp(aOldName, Server()->ClientName(ClientID)) != 0)
 		{
 			char aChatText[256];
 			str_format(aChatText, sizeof(aChatText), "'%s' changed name to '%s'", aOldName, Server()->ClientName(ClientID));
 			SendChat(-1, CGameContext::CHAT_ALL, aChatText);
 		}
-		
-		// set skin
 		str_copy(pPlayer->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_SkinName));
-		
+		pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor;
+		pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody;
+		pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet;
 		m_pController->OnPlayerInfoChange(pPlayer);
-		
-		if(MsgID == NETMSGTYPE_CL_STARTINFO)
-		{
-			// send vote options
-			CNetMsg_Sv_VoteClearOptions ClearMsg;
-			Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientID);
-			CVoteOption *pCurrent = m_pVoteOptionFirst;
-			while(pCurrent)
-			{
-				CNetMsg_Sv_VoteOption OptionMsg;
-				OptionMsg.m_pCommand = pCurrent->m_aCommand;
-				Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, ClientID);
-				pCurrent = pCurrent->m_pNext;
-			}
-			
-			// send tuning parameters to client
-			SendTuningParams(ClientID);
-
-			//
-			CNetMsg_Sv_ReadyToEnter m;
-			Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID);
-		}
 	}
 	else if (MsgID == NETMSGTYPE_CL_EMOTICON && !m_World.m_Paused)
 	{
@@ -1167,6 +1175,11 @@ void CGameContext::OnPostSnap()
 	m_Events.Clear();
 }
 
+bool CGameContext::IsClientReady(int ClientID)
+{
+	return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReady ? true : false;
+}
+
 const char *CGameContext::GameType() { return m_pController && m_pController->m_pGameType ? m_pController->m_pGameType : ""; }
 const char *CGameContext::Version() { return GAME_VERSION; }
 const char *CGameContext::NetVersion() { return GAME_NETVERSION; }
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 7ca38973..f64232a1 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -161,6 +161,8 @@ public:
 	virtual void OnClientDirectInput(int ClientID, void *pInput);
 	virtual void OnClientPredictedInput(int ClientID, void *pInput);
 
+	virtual bool IsClientReady(int ClientID);
+
 	virtual const char *GameType();
 	virtual const char *Version();
 	virtual const char *NetVersion();
diff --git a/src/game/server/player.h b/src/game/server/player.h
index c638fae8..d91bc346 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -47,6 +47,8 @@ public:
 
 	// used for spectator mode
 	int m_SpectatorID;
+
+	bool m_IsReady;
 	
 	//
 	int m_Vote;