about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/base/system.c44
-rw-r--r--src/engine/console.h5
-rw-r--r--src/engine/server/server.cpp127
-rw-r--r--src/engine/server/server.h29
-rw-r--r--src/engine/shared/config_variables.h10
-rw-r--r--src/engine/shared/console.cpp32
-rw-r--r--src/engine/shared/console.h12
-rw-r--r--src/engine/shared/econ.cpp147
-rw-r--r--src/engine/shared/econ.h43
-rw-r--r--src/engine/shared/network.h24
-rw-r--r--src/engine/shared/network_console.cpp49
-rw-r--r--src/engine/shared/network_console_conn.cpp172
-rw-r--r--src/game/client/components/console.cpp14
-rw-r--r--src/game/client/components/console.h2
14 files changed, 408 insertions, 302 deletions
diff --git a/src/base/system.c b/src/base/system.c
index 551b3f1b..466e3ca6 100644
--- a/src/base/system.c
+++ b/src/base/system.c
@@ -42,10 +42,6 @@
 	#include <fcntl.h>
 	#include <direct.h>
 	#include <errno.h>
-
-	#ifndef EWOULDBLOCK
-		#define EWOULDBLOCK WSAEWOULDBLOCK
-	#endif
 #else
 	#error NOT IMPLEMENTED
 #endif
@@ -1102,30 +1098,31 @@ int net_set_blocking(NETSOCKET sock)
 
 int net_tcp_listen(NETSOCKET sock, int backlog)
 {
+	int err = -1;
 	if(sock.ipv4sock >= 0)
-		listen(sock.ipv4sock, backlog);
+		err = listen(sock.ipv4sock, backlog);
 	if(sock.ipv6sock >= 0)
-		listen(sock.ipv6sock, backlog);
-	return 0;
+		err = listen(sock.ipv6sock, backlog);
+	return err;
 }
 
 int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
 {
 	int s;
 	socklen_t sockaddr_len;
-	struct sockaddr addr;
 
 	*new_sock = invalid_socket;
 
-	sockaddr_len = sizeof(addr);
-
 	if(sock.ipv4sock >= 0)
 	{
-		s = accept(sock.ipv4sock, &addr, &sockaddr_len);
+		struct sockaddr_in addr;
+		sockaddr_len = sizeof(addr);
 
+		s = accept(sock.ipv4sock, (struct sockaddr *)&addr, &sockaddr_len);
+		
 		if (s != -1)
 		{
-			sockaddr_to_netaddr(&addr, a);
+			sockaddr_to_netaddr((const struct sockaddr *)&addr, a);
 			new_sock->type = NETTYPE_IPV4;
 			new_sock->ipv4sock = s;
 			return s;
@@ -1134,18 +1131,21 @@ int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
 
 	if(sock.ipv6sock >= 0)
 	{
-		s = accept(sock.ipv6sock, &addr, &sockaddr_len);
+		struct sockaddr_in6 addr;
+		sockaddr_len = sizeof(addr);
 
+		s = accept(sock.ipv6sock, (struct sockaddr *)&addr, &sockaddr_len);
+		
 		if (s != -1)
 		{
-			sockaddr_to_netaddr(&addr, a);
+			sockaddr_to_netaddr((const struct sockaddr *)&addr, a);
 			new_sock->type = NETTYPE_IPV6;
 			new_sock->ipv6sock = s;
 			return s;
 		}
 	}
 
-	return 0;
+	return -1;
 }
 
 int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
@@ -1164,7 +1164,7 @@ int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
 		return connect(sock.ipv6sock, (struct sockaddr *)&addr, sizeof(addr));
 	}
 
-	return 0;
+	return -1;
 }
 
 int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
@@ -1180,7 +1180,7 @@ int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
 
 int net_tcp_send(NETSOCKET sock, const void *data, int size)
 {
-	int bytes = 0;
+	int bytes = -1;
 
 	if(sock.ipv4sock >= 0)
 		bytes = send((int)sock.ipv4sock, (const char*)data, size, 0);
@@ -1192,7 +1192,7 @@ int net_tcp_send(NETSOCKET sock, const void *data, int size)
 
 int net_tcp_recv(NETSOCKET sock, void *data, int maxsize)
 {
-	int bytes = 0;
+	int bytes = -1;
 
 	if(sock.ipv4sock >= 0)
 		bytes = recv((int)sock.ipv4sock, (char*)data, maxsize, 0);
@@ -1209,12 +1209,20 @@ int net_tcp_close(NETSOCKET sock)
 
 int net_errno()
 {
+#if defined(CONF_FAMILY_WINDOWS)
+	return WSAGetLastError();
+#else
 	return errno;
+#endif
 }
 
 int net_would_block()
 {
+#if defined(CONF_FAMILY_WINDOWS)
+	return net_errno() == WSAEWOULDBLOCK;
+#else
 	return net_errno() == EWOULDBLOCK;
+#endif
 }
 
 int net_init()
diff --git a/src/engine/console.h b/src/engine/console.h
index 7c39cf49..5d3f2811 100644
--- a/src/engine/console.h
+++ b/src/engine/console.h
@@ -23,6 +23,8 @@ public:
 		TEMPCMD_NAME_LENGTH=32,
 		TEMPCMD_HELP_LENGTH=64,
 		TEMPCMD_PARAMS_LENGTH=16,
+
+		MAX_PRINT_CB=4,
 	};
 
 	// TODO: rework this interface to reduce the amount of virtual calls
@@ -79,7 +81,8 @@ public:
 	virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0;
 	virtual void ExecuteFile(const char *pFilename) = 0;
 
-	virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) = 0;
+	virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0;
+	virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0;
 	virtual void Print(int Level, const char *pFrom, const char *pStr) = 0;
 
 	virtual void SetAccessLevel(int AccessLevel) = 0;
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 8e151035..ee7de31b 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -16,6 +16,7 @@
 #include <engine/shared/config.h>
 #include <engine/shared/datafile.h>
 #include <engine/shared/demo.h>
+#include <engine/shared/econ.h>
 #include <engine/shared/mapchecker.h>
 #include <engine/shared/network.h>
 #include <engine/shared/packer.h>
@@ -317,15 +318,8 @@ int CServer::Init()
 		m_aClients[i].m_Snapshots.Init();
 	}
 
-	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
-	{
-		m_aEconClients[i].m_State = CEconClient::STATE_EMPTY;
-	}
-
 	m_CurrentGameTick = 0;
 
-	m_UseEcon = 0;
-
 	return 0;
 }
 
@@ -618,30 +612,6 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
 	return 0;
 }
 
-int CServer::NewConsoleClientCallback(int EconID, void *pUser)
-{
-	CServer *pThis = (CServer *)pUser;
-	pThis->m_aEconClients[EconID].m_State = CEconClient::STATE_CONNECTED;
-	pThis->m_NetConsole.SetTimeout(EconID, g_Config.m_SvEconAuthTimeout);
-	return 0;
-}
-
-int CServer::DelConsoleClientCallback(int EconID, const char *pReason, void *pUser)
-{
-	CServer *pThis = (CServer *)pUser;
-
-	NETADDR Addr = pThis->m_NetConsole.ClientAddr(EconID);
-	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-	char aBuf[256];
-	str_format(aBuf, sizeof(aBuf), "econ client dropped. eid=%d addr=%s reason='%s'", EconID, aAddrStr, pReason);
-	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
-
-	pThis->m_aEconClients[EconID].m_State = CEconClient::STATE_EMPTY;
-	return 0;
-}
-
-
 void CServer::SendMap(int ClientID)
 {
 	CMsgPacker Msg(NETMSG_MAP_CHANGE);
@@ -664,12 +634,7 @@ void CServer::SendRconLine(int ClientID, const char *pLine)
 	SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
 }
 
-void CServer::SendEconLine(int EconID, const char *pLine)
-{
-	m_NetConsole.Send(EconID, pLine);
-}
-
-void CServer::SendConsoleLineAuthed(const char *pLine, void *pUser)
+void CServer::SendRconLineAuthed(const char *pLine, void *pUser)
 {
 	CServer *pThis = (CServer *)pUser;
 	static volatile int ReentryGuard = 0;
@@ -684,15 +649,6 @@ void CServer::SendConsoleLineAuthed(const char *pLine, void *pUser)
 			pThis->SendRconLine(i, pLine);
 	}
 
-	if(pThis->m_UseEcon)
-	{
-		for(i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
-		{
-			if(pThis->m_aEconClients[i].m_State == CEconClient::STATE_AUTHED)
-				pThis->SendEconLine(i, pLine);
-		}
-	}
-
 	ReentryGuard--;
 }
 
@@ -1130,46 +1086,7 @@ void CServer::PumpNetwork()
 			ProcessClientPacket(&Packet);
 	}
 
-	if(m_UseEcon)
-		EconPumpNetwork();
-}
-
-void CServer::EconPumpNetwork()
-{
-	m_NetConsole.Update();
-
-	char aBuf[NET_MAX_PACKETSIZE];
-	int EconID;
-
-	while(m_NetConsole.Recv(aBuf, sizeof(aBuf) - 1, &EconID))
-	{
-		dbg_assert(m_aEconClients[EconID].m_State != CEconClient::STATE_EMPTY, "got message from empty slot");
-		if(m_aEconClients[EconID].m_State == CEconClient::STATE_CONNECTED)
-		{
-			if(str_comp(aBuf, g_Config.m_SvRconPassword) == 0)
-			{
-				m_aEconClients[EconID].m_State = CEconClient::STATE_AUTHED;
-				m_NetConsole.Send(EconID, "Authentication successful. Remote console access granted.");
-				m_NetConsole.SetTimeout(EconID, g_Config.m_SvEconTimeout);
-
-				str_format(aBuf, sizeof(aBuf), "EconID=%d authed", EconID);
-				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-			}
-			else
-			{
-				m_NetConsole.Send(EconID, "Wrong password");
-			}
-		}
-		else if(m_aEconClients[EconID].m_State == CEconClient::STATE_AUTHED)
-		{
-			char aFormatted[256];
-			str_format(aFormatted, sizeof(aBuf), "eid=%d cmd='%s'", EconID, aBuf);
-			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted);
-			m_RconClientID = EconID;
-			Console()->ExecuteLine(aBuf);
-			m_RconClientID = -1;
-		}
-	}
+	m_Econ.Update();
 }
 
 char *CServer::GetMapName()
@@ -1244,7 +1161,7 @@ int CServer::Run()
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
 
 	//
-	Console()->RegisterPrintCallback(SendConsoleLineAuthed, this);
+	m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this);
 
 	// load map
 	if(!LoadMap(g_Config.m_SvMap))
@@ -1275,30 +1192,7 @@ int CServer::Run()
 
 	m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this);
 
-	if(g_Config.m_SvEconPort && g_Config.m_SvRconPassword[0])
-	{
-		dbg_msg("econ", "binding econ to %s:%d", g_Config.m_SvEconBindaddr, g_Config.m_SvEconPort);
-		if(g_Config.m_SvEconBindaddr[0] && net_host_lookup(g_Config.m_SvEconBindaddr, &BindAddr, NETTYPE_ALL) == 0)
-		{
-			BindAddr.port = g_Config.m_SvEconPort;
-		}
-		else
-		{
-			mem_zero(&BindAddr, sizeof(BindAddr));
-			BindAddr.type = NETTYPE_ALL;
-			BindAddr.port = g_Config.m_SvEconPort;
-		}
-
-		if(m_NetConsole.Open(BindAddr, 0))
-		{
-			m_NetConsole.SetCallbacks(NewConsoleClientCallback, DelConsoleClientCallback, this);
-			m_UseEcon = 1;
-		}
-		else
-		{
-			dbg_msg("econ", "couldn't open econ socket. port might already be in use");
-		}
-	}
+	m_Econ.Init(Console());
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName);
@@ -1694,6 +1588,16 @@ void CServer::ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserDa
 		pfnCallback(pResult, pCallbackUserData);
 }
 
+void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CServer *pThis = static_cast<CServer *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
 void CServer::RegisterCommands()
 {
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
@@ -1715,6 +1619,7 @@ void CServer::RegisterCommands()
 
 	Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this);
 	Console()->Chain("mod_command", ConchainModCommandUpdate, this);
+	Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this);
 }
 
 
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index d744b9ff..4e575055 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -109,26 +109,11 @@ public:
 
 	CClient m_aClients[MAX_CLIENTS];
 
-	class CEconClient
-	{
-	public:
-		enum
-		{
-			STATE_EMPTY=0,
-			STATE_CONNECTED,
-			STATE_AUTHED
-		};
-
-		int m_State;
-	};
-
-	CEconClient m_aEconClients[NET_MAX_CONSOLE_CLIENTS];
-
 	CSnapshotDelta m_SnapshotDelta;
 	CSnapshotBuilder m_SnapshotBuilder;
 	CSnapIDPool m_IDPool;
 	CNetServer m_NetServer;
-	CNetConsole m_NetConsole;
+	CEcon m_Econ;
 
 	IEngineMap *m_pMap;
 
@@ -138,8 +123,7 @@ public:
 	int m_MapReload;
 	int m_RconClientID;
 	int m_RconAuthLevel;
-
-	int m_UseEcon;
+	int m_PrintCBIndex;
 
 	int64 m_Lastheartbeat;
 	//static NETADDR4 master_server;
@@ -186,14 +170,10 @@ public:
 	static int NewClientCallback(int ClientID, void *pUser);
 	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
 
-	static int NewConsoleClientCallback(int EconID, void *pUser);
-	static int DelConsoleClientCallback(int EconID, const char *pReason, void *pUser);
-
 	void SendMap(int ClientID);
 	void SendConnectionReady(int ClientID);
 	void SendRconLine(int ClientID, const char *pLine);
-	void SendEconLine(int EconID, const char *pLine);
-	static void SendConsoleLineAuthed(const char *pLine, void *pUser);
+	static void SendRconLineAuthed(const char *pLine, void *pUser);
 
 	void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID);
 	void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID);
@@ -207,8 +187,6 @@ public:
 	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
 	int BanRemove(NETADDR Addr);
 
-	void EconPumpNetwork();
-
 	void PumpNetwork();
 
 	char *GetMapName();
@@ -229,6 +207,7 @@ public:
 	static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+	static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 	void RegisterCommands();
 
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index fd318a7d..cb8f5f15 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -86,10 +86,12 @@ MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remo
 MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER, "Remote console password for moderators (limited access)")
 MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 3, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication")
 MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick")
-MACRO_CONFIG_STR(SvEconBindaddr, sv_econ_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous")
-MACRO_CONFIG_INT(SvEconPort, sv_econ_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console")
-MACRO_CONFIG_INT(SvEconAuthTimeout, sv_econ_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out")
-MACRO_CONFIG_INT(SvEconTimeout, sv_econ_timeout, 300, 1, 3600, CFGFLAG_SERVER, "Time in seconds before the econ connection times out")
+
+MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous")
+MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console")
+MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_SERVER, "External console password")
+MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out")
+MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_SERVER, "Adjusts the amount of information in the external console")
 
 MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode")
 MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems")
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
index 847fb140..e4cb1991 100644
--- a/src/engine/shared/console.cpp
+++ b/src/engine/shared/console.cpp
@@ -173,20 +173,34 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
 	return Error;
 }
 
-void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData)
+int CConsole::RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData)
 {
-	m_pfnPrintCallback = pfnPrintCallback;
-	m_pPrintCallbackUserdata = pUserData;
+	if(m_NumPrintCB == MAX_PRINT_CB)
+		return -1;
+
+	m_aPrintCB[m_NumPrintCB].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG));
+	m_aPrintCB[m_NumPrintCB].m_pfnPrintCallback = pfnPrintCallback;
+	m_aPrintCB[m_NumPrintCB].m_pPrintCallbackUserdata = pUserData;
+	return m_NumPrintCB++;
+}
+
+void CConsole::SetPrintOutputLevel(int Index, int OutputLevel)
+{
+	if(Index >= 0 && Index < MAX_PRINT_CB)
+		m_aPrintCB[Index].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG));
 }
 
 void CConsole::Print(int Level, const char *pFrom, const char *pStr)
 {
 	dbg_msg(pFrom ,"%s", pStr);
-	if(Level <= g_Config.m_ConsoleOutputLevel && m_pfnPrintCallback)
+	for(int i = 0; i < m_NumPrintCB; ++i)
 	{
-		char aBuf[1024];
-		str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr);
-		m_pfnPrintCallback(aBuf, m_pPrintCallbackUserdata);
+		if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback)
+		{
+			char aBuf[1024];
+			str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr);
+			m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata);
+		}
 	}
 }
 
@@ -562,8 +576,8 @@ CConsole::CConsole(int FlagMask)
 	m_ExecutionQueue.Reset();
 	m_pFirstCommand = 0;
 	m_pFirstExec = 0;
-	m_pPrintCallbackUserdata = 0;
-	m_pfnPrintCallback = 0;
+	mem_zero(m_aPrintCB, sizeof(m_aPrintCB));
+	m_NumPrintCB = 0;
 
 	m_pStorage = 0;
 
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
index b29f3202..6989c696 100644
--- a/src/engine/shared/console.h
+++ b/src/engine/shared/console.h
@@ -60,8 +60,13 @@ class CConsole : public IConsole
 	void ExecuteFileRecurse(const char *pFilename);
 	void ExecuteLineStroked(int Stroke, const char *pStr);
 
-	FPrintCallback m_pfnPrintCallback;
-	void *m_pPrintCallbackUserdata;
+	struct
+	{
+		int m_OutputLevel;
+		FPrintCallback m_pfnPrintCallback;
+		void *m_pPrintCallbackUserdata;
+	} m_aPrintCB[MAX_PRINT_CB];
+	int m_NumPrintCB;
 
 	enum
 	{
@@ -167,7 +172,8 @@ public:
 	virtual void ExecuteLine(const char *pStr);
 	virtual void ExecuteFile(const char *pFilename);
 
-	virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData);
+	virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData);
+	virtual void SetPrintOutputLevel(int Index, int OutputLevel);
 	virtual void Print(int Level, const char *pFrom, const char *pStr);
 
 	void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); }
diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp
new file mode 100644
index 00000000..ba86e5c0
--- /dev/null
+++ b/src/engine/shared/econ.cpp
@@ -0,0 +1,147 @@
+#include <engine/console.h>
+#include <engine/shared/config.h>
+
+#include "econ.h"
+
+int CEcon::NewClientCallback(int ClientID, void *pUser)
+{
+	CEcon *pThis = (CEcon *)pUser;
+
+	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	char aBuf[128];
+	str_format(aBuf, sizeof(aBuf), "client accepted. cid=%d addr=%s'", ClientID, aAddrStr);
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
+
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED;
+	pThis->m_aClients[ClientID].m_TimeConnected = time_get();
+	return 0;
+}
+
+int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser)
+{
+	CEcon *pThis = (CEcon *)pUser;
+
+	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	char aBuf[256];
+	str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
+
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
+	return 0;
+}
+
+void CEcon::SendLineCB(const char *pLine, void *pUserData)
+{
+	static_cast<CEcon *>(pUserData)->Send(-1, pLine);
+}
+
+void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CEcon *pThis = static_cast<CEcon *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
+void CEcon::Init(IConsole *pConsole)
+{
+	m_pConsole = pConsole;
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		m_aClients[i].m_State = CClient::STATE_EMPTY;
+
+	m_Ready = false;
+
+	if(g_Config.m_EcPort == 0 || g_Config.m_EcPassword[0] == 0)
+		return;
+
+	NETADDR BindAddr;
+	if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0)
+		BindAddr.port = g_Config.m_EcPort;
+	else
+	{
+		mem_zero(&BindAddr, sizeof(BindAddr));
+		BindAddr.type = NETTYPE_ALL;
+		BindAddr.port = g_Config.m_EcPort;
+	}
+
+	if(m_NetConsole.Open(BindAddr, 0))
+	{
+		m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this);
+		m_Ready = true;
+		char aBuf[128];
+		str_format(aBuf, sizeof(aBuf), "bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", aBuf);
+
+		Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this);
+		m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this);
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", "couldn't open socket. port might already be in use");
+}
+
+void CEcon::Update()
+{
+	if(!m_Ready)
+		return;
+
+	m_NetConsole.Update();
+
+	char aBuf[NET_MAX_PACKETSIZE];
+	int ClientID;
+
+	while(m_NetConsole.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID))
+	{
+		dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "got message from empty slot");
+		if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED)
+		{
+			if(str_comp(aBuf, g_Config.m_EcPassword) == 0)
+			{
+				m_aClients[ClientID].m_State = CClient::STATE_AUTHED;
+				m_NetConsole.Send(ClientID, "Authentication successful. External console access granted.");
+
+				str_format(aBuf, sizeof(aBuf), "cid=%d authed", ClientID);
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf);
+			}
+			else
+				m_NetConsole.Send(ClientID, "Wrong password");
+		}
+		else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		{
+			char aFormatted[256];
+			str_format(aFormatted, sizeof(aBuf), "cid=%d cmd='%s'", ClientID, aBuf);
+			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted);
+			Console()->ExecuteLine(aBuf);
+		}
+	}
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; ++i)
+	{
+		if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
+			time_get() > m_aClients[i].m_TimeConnected + g_Config.m_EcAuthTimeout * time_freq())
+			m_NetConsole.Drop(i, "authentication timeout");
+	}
+}
+
+void CEcon::Send(int ClientID, const char *pLine)
+{
+	if(!m_Ready)
+		return;
+
+	if(ClientID == -1)
+	{
+		for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		{
+			if(m_aClients[i].m_State == CClient::STATE_AUTHED)
+				m_NetConsole.Send(i, pLine);
+		}
+	}
+	else if(ClientID >= 0 && ClientID < NET_MAX_CONSOLE_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		m_NetConsole.Send(ClientID, pLine);
+}
diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h
new file mode 100644
index 00000000..33b23ea6
--- /dev/null
+++ b/src/engine/shared/econ.h
@@ -0,0 +1,43 @@
+#ifndef ENGINE_SHARED_ECON_H
+#define ENGINE_SHARED_ECON_H
+
+#include "network.h"
+
+class CEcon
+{
+	class CClient
+	{
+	public:
+		enum
+		{
+			STATE_EMPTY=0,
+			STATE_CONNECTED,
+			STATE_AUTHED,
+		};
+
+		int m_State;
+		int64 m_TimeConnected;
+	};
+	CClient m_aClients[NET_MAX_CONSOLE_CLIENTS];
+
+	IConsole *m_pConsole;
+	CNetConsole m_NetConsole;
+
+	bool m_Ready;
+	int m_PrintCBIndex;
+
+	static void SendLineCB(const char *pLine, void *pUserData);
+	static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+
+	static int NewClientCallback(int ClientID, void *pUser);
+	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
+
+public:
+	IConsole *Console() { return m_pConsole; }
+
+	void Init(IConsole *pConsole);
+	void Update();
+	void Send(int ClientID, const char *pLine);
+};
+
+#endif
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index d0b78d05..94e2824c 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -49,7 +49,7 @@ enum
 	NET_MAX_CHUNKHEADERSIZE = 5,
 	NET_PACKETHEADERSIZE = 3,
 	NET_MAX_CLIENTS = 16,
-	NET_MAX_CONSOLE_CLIENTS = 16,
+	NET_MAX_CONSOLE_CLIENTS = 4,
 	NET_MAX_SEQUENCE = 1<<10,
 	NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1,
 
@@ -196,32 +196,27 @@ public:
 class CConsoleNetConnection
 {
 private:
-	unsigned m_State;
+	int m_State;
 
 	NETADDR m_PeerAddr;
 	NETSOCKET m_Socket;
 
 	char m_aBuffer[NET_MAX_PACKETSIZE];
-	char *m_pBufferPos;
+	int m_BufferOffset;
 
 	char m_aErrorString[256];
 
-	int m_Timeout;
-	int64 m_LastRecvTime;
+	bool m_LineEndingDetected;
+	char m_aLineEnding[3];
 
 public:
-	void Init(NETSOCKET Socket);
 	void Init(NETSOCKET Socket, const NETADDR *pAddr);
-	int Connect(const NETADDR *pAddr);
 	void Disconnect(const char *pReason);
 
 	int State() const { return m_State; }
 	NETADDR PeerAddress() const { return m_PeerAddr; }
 	const char *ErrorString() const { return m_aErrorString; }
 
-	void SetTimeout(int Timeout) { m_Timeout = Timeout; }
-	int Timeout() const { return m_Timeout; }
-
 	void Reset();
 	int Update();
 	int Send(const char *pLine);
@@ -337,7 +332,7 @@ private:
 	};
 
 	NETSOCKET m_Socket;
-	CSlot m_aSlots[NET_MAX_CLIENTS];
+	CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
 
 	NETFUNC_NEWCLIENT m_pfnNewClient;
 	NETFUNC_DELCLIENT m_pfnDelClient;
@@ -346,14 +341,13 @@ private:
 	CNetRecvUnpacker m_RecvUnpacker;
 
 public:
-	int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+	void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
 
 	//
 	bool Open(NETADDR BindAddr, int Flags);
 	int Close();
 
 	//
-	int Broadcast(const char *pLine);
 	int Recv(char *pLine, int MaxLength, int *pClientID = 0);
 	int Send(int ClientID, const char *pLine);
 	int Update();
@@ -362,11 +356,7 @@ public:
 	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
 	int Drop(int ClientID, const char *pReason);
 
-	//
-	void SetTimeout(int ClientID, int Timeout) { m_aSlots[ClientID].m_Connection.SetTimeout(Timeout); }
-
 	// status requests
-	int Timeout(int ClientID) { return m_aSlots[ClientID].m_Connection.Timeout(); }
 	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
 };
 
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
index 3e50a1ac..0cf2a718 100644
--- a/src/engine/shared/network_console.cpp
+++ b/src/engine/shared/network_console.cpp
@@ -9,25 +9,24 @@ bool CNetConsole::Open(NETADDR BindAddr, int Flags)
 	mem_zero(this, sizeof(*this));
 
 	// open socket
-	m_Socket = net_tcp_create(&BindAddr);
+	m_Socket = net_tcp_create(BindAddr);
 	if(!m_Socket.type)
 		return false;
 	if(net_tcp_listen(m_Socket, NET_MAX_CONSOLE_CLIENTS))
 		return false;
-	net_tcp_set_non_blocking(m_Socket);
+	net_set_non_blocking(m_Socket);
 
-	for(int i = 0; i < NET_MAX_CLIENTS; i++)
-		m_aSlots[i].m_Connection.Init(m_Socket);
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Reset();
 
 	return true;
 }
 
-int CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
 {
 	m_pfnNewClient = pfnNewClient;
 	m_pfnDelClient = pfnDelClient;
 	m_UserPtr = pUser;
-	return 0;
 }
 
 int CNetConsole::Close()
@@ -38,11 +37,6 @@ int CNetConsole::Close()
 
 int CNetConsole::Drop(int ClientID, const char *pReason)
 {
-	NETADDR Addr = ClientAddr(ClientID);
-
-	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-
 	if(m_pfnDelClient)
 		m_pfnDelClient(ClientID, pReason, m_UserPtr);
 
@@ -56,40 +50,39 @@ int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
 	char aError[256] = { 0 };
 	int FreeSlot = -1;
 	
+	// look for free slot or multiple client
 	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
 	{
 		if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
 			FreeSlot = i;
 		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
 		{
-			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();;
+			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
 			if(net_addr_comp(pAddr, &PeerAddr) == 0)
 			{
-				str_copy(aError, "Only one client per IP allowed", sizeof(aError));
+				str_copy(aError, "only one client per IP allowed", sizeof(aError));
 				break;
 			}
 		}
 	}
 
+	// accept client
 	if(!aError[0] && FreeSlot != -1)
 	{
 		m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
 		if(m_pfnNewClient)
 			m_pfnNewClient(FreeSlot, m_UserPtr);
-		return 1;
+		return 0;
 	}
 
+	// reject client
 	if(!aError[0])
-	{
-		str_copy(aError, "No free slot available", sizeof(aError));
-	}
-	
-	dbg_msg("netconsole", "refused client, reason=\"%s\"", aError);
+		str_copy(aError, "no free slot available", sizeof(aError));
 
 	net_tcp_send(Socket, aError, str_length(aError));
 	net_tcp_close(Socket);
 
-	return 0;
+	return -1;
 }
 
 int CNetConsole::Update()
@@ -127,18 +120,10 @@ int CNetConsole::Recv(char *pLine, int MaxLength, int *pClientID)
 	return 0;
 }
 
-int CNetConsole::Broadcast(const char *pLine)
-{
-	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
-	{
-		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
-			Send(i, pLine);
-	}
-	return 0;
-}
-
 int CNetConsole::Send(int ClientID, const char *pLine)
 {
-	return m_aSlots[ClientID].m_Connection.Send(pLine);
+	if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE)
+		return m_aSlots[ClientID].m_Connection.Send(pLine);
+	else
+		return -1;
 }
-
diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp
index 4dd971a6..75b581fa 100644
--- a/src/engine/shared/network_console_conn.cpp
+++ b/src/engine/shared/network_console_conn.cpp
@@ -1,7 +1,6 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
 #include <base/system.h>
-#include "config.h"
 #include "network.h"
 
 void CConsoleNetConnection::Reset()
@@ -10,9 +9,22 @@ void CConsoleNetConnection::Reset()
 	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
 	m_aErrorString[0] = 0;
 
+	m_Socket.type = NETTYPE_INVALID;
+	m_Socket.ipv4sock = -1;
+	m_Socket.ipv6sock = -1;
 	m_aBuffer[0] = 0;
-	m_pBufferPos = 0;
-	m_Timeout = 0;
+	m_BufferOffset = 0;
+
+	m_LineEndingDetected = false;
+	#if defined(CONF_FAMILY_WINDOWS)
+		m_aLineEnding[0] = '\r';
+		m_aLineEnding[1] = '\n';
+		m_aLineEnding[2] = 0;
+	#else
+		m_aLineEnding[0] = '\n';
+		m_aLineEnding[1] = 0;
+		m_aLineEnding[2] = 0;
+	#endif
 }
 
 void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr)
@@ -20,48 +32,19 @@ void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr)
 	Reset();
 
 	m_Socket = Socket;
-	net_tcp_set_non_blocking(m_Socket);
-
-	m_LastRecvTime = time_get();
+	net_set_non_blocking(m_Socket);
 
 	m_PeerAddr = *pAddr;
 	m_State = NET_CONNSTATE_ONLINE;
 }
 
-void CConsoleNetConnection::Init(NETSOCKET Socket)
-{
-	Reset();
-
-	m_Socket = Socket;
-	net_tcp_set_non_blocking(m_Socket);
-
-	m_LastRecvTime = time_get();
-}
-
-int CConsoleNetConnection::Connect(const NETADDR *pAddr)
-{
-	if(State() != NET_CONNSTATE_OFFLINE)
-		return -1;
-
-	// init connection
-	Reset();
-	m_PeerAddr = *pAddr;
-	net_tcp_connect(m_Socket, pAddr);
-	m_State = NET_CONNSTATE_ONLINE;
-	return 0;
-}
-
 void CConsoleNetConnection::Disconnect(const char *pReason)
 {
 	if(State() == NET_CONNSTATE_OFFLINE)
 		return;
 
-	if(pReason)
-	{
-		char aBuf[sizeof(pReason) + 4];
-		str_format(aBuf, sizeof(aBuf), "%s", pReason);
-		Send(aBuf);
-	}
+	if(pReason && pReason[0])
+		Send(pReason);
 
 	net_tcp_close(m_Socket);
 
@@ -70,32 +53,20 @@ void CConsoleNetConnection::Disconnect(const char *pReason)
 
 int CConsoleNetConnection::Update()
 {
-	if(m_Timeout && time_get() > m_LastRecvTime + m_Timeout * time_freq())
-	{
-		m_State = NET_CONNSTATE_ERROR;
-		str_copy(m_aErrorString, "timeout", sizeof(m_aErrorString));
-		return -1;
-	}
-
 	if(State() == NET_CONNSTATE_ONLINE)
 	{
-		char aBuf[NET_MAX_PACKETSIZE];
+		if((int)(sizeof(m_aBuffer)) <= m_BufferOffset)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString));
+			return -1;
+		}
 
-		int Bytes = net_tcp_recv(m_Socket, aBuf, sizeof(aBuf) - 1);
+		int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset);
 
 		if(Bytes > 0)
 		{
-			aBuf[Bytes - 1] = 0;
-
-			if(!m_pBufferPos)
-				m_aBuffer[0] = 0;
-			else if(m_pBufferPos != m_aBuffer)
-				mem_move(m_pBufferPos, m_aBuffer, str_length(m_pBufferPos) + 1); // +1 for the \0
-			m_pBufferPos = m_aBuffer;
-
-			str_append(m_aBuffer, aBuf, sizeof(m_aBuffer));
-
-			m_LastRecvTime = time_get();
+			m_BufferOffset += Bytes;
 		}
 		else if(Bytes < 0)
 		{
@@ -121,32 +92,59 @@ int CConsoleNetConnection::Recv(char *pLine, int MaxLength)
 {
 	if(State() == NET_CONNSTATE_ONLINE)
 	{
-		if(m_pBufferPos && *m_pBufferPos)
+		if(m_BufferOffset)
 		{
-			char *pResult = m_pBufferPos;
-
-			while(*m_pBufferPos && *m_pBufferPos != '\r' && *m_pBufferPos != '\n')
-				m_pBufferPos++;
-
-			if(*m_pBufferPos) // haven't reached the end of the buffer?
+			// find message start
+			int StartOffset = 0;
+			while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n')
 			{
-				if(*m_pBufferPos == '\r' && *(m_pBufferPos + 1) == '\n')
+				// detect clients line ending format
+				if(!m_LineEndingDetected)
 				{
-					*m_pBufferPos = 0;
-					m_pBufferPos += 2;
+					m_aLineEnding[0] = m_aBuffer[StartOffset];
+					if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') &&
+						m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1])
+						m_aLineEnding[1] = m_aBuffer[StartOffset+1];
+					m_LineEndingDetected = true;
 				}
-				else
+
+				if(++StartOffset >= m_BufferOffset)
 				{
-					*m_pBufferPos = 0;
-					m_pBufferPos++;
+					m_BufferOffset = 0;
+					return 0;
 				}
 			}
-			else
+
+			// find message end
+			int EndOffset = StartOffset;
+			while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n')
 			{
-				m_pBufferPos = 0;
+				if(++EndOffset >= m_BufferOffset)
+				{
+					if(StartOffset > 0)
+					{
+						mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+						m_BufferOffset -= StartOffset;
+					}
+					return 0;
+				}
 			}
 
-			str_copy(pLine, pResult, MaxLength);
+			// extract message and update buffer
+			if(MaxLength-1 < EndOffset-StartOffset)
+			{
+				if(StartOffset > 0)
+				{
+					mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+					m_BufferOffset -= StartOffset;
+				}
+				return 0;
+			}
+			mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset);
+			pLine[EndOffset-StartOffset] = 0;
+			str_sanitize_cc(pLine);
+			mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset);
+			m_BufferOffset -= EndOffset;
 			return 1;
 		}
 	}
@@ -158,19 +156,31 @@ int CConsoleNetConnection::Send(const char *pLine)
 	if(State() != NET_CONNSTATE_ONLINE)
 		return -1;
 
-	int Length = str_length(pLine);
 	char aBuf[1024];
-	str_copy(aBuf, pLine, sizeof(aBuf) - 2);
-	aBuf[Length + 1] = '\n';
-	aBuf[Length + 2] = '\0';
-
-	if(net_tcp_send(m_Socket, aBuf, Length + 2) < 0)
+	str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2);
+	int Length = str_length(aBuf);
+	aBuf[Length] = m_aLineEnding[0];
+	aBuf[Length+1] = m_aLineEnding[1];
+	aBuf[Length+2] = m_aLineEnding[2];
+	Length += 3;
+	const char *pData = aBuf;
+
+	while(true)
 	{
-		m_State = NET_CONNSTATE_ERROR;
-		str_copy(m_aErrorString, "Failed to send packet", sizeof(m_aErrorString));
-		return -1;
+		int Send = net_tcp_send(m_Socket, pData, Length);
+		if(Send < 0)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString));
+			return -1;
+		}
+
+		if(Send >= Length)
+			break;
+
+		pData += Send;
+		Length -= Send;
 	}
 	
 	return 0;
 }
-
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index 9b16ce0d..f2e9e65d 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -662,6 +662,16 @@ void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData)
 	((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr);
 }
 
+void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CGameConsole *pThis = static_cast<CGameConsole *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
 void CGameConsole::PrintLine(int Type, const char *pLine)
 {
 	if(Type == CONSOLETYPE_LOCAL)
@@ -679,7 +689,7 @@ void CGameConsole::OnConsoleInit()
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
 
 	//
-	Console()->RegisterPrintCallback(ClientConsolePrintCallback, this);
+	m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this);
 
 	Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console");
 	Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console");
@@ -687,6 +697,8 @@ void CGameConsole::OnConsoleInit()
 	Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console");
 	Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console");
 	Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console");
+
+	Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this);
 }
 
 void CGameConsole::OnStateChange(int NewState, int OldState)
diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h
index 326fb076..6bcc75a6 100644
--- a/src/game/client/components/console.h
+++ b/src/game/client/components/console.h
@@ -60,6 +60,7 @@ class CGameConsole : public CComponent
 
 	CInstance *CurrentConsole();
 	float TimeNow();
+	int m_PrintCBIndex;
 
 	int m_ConsoleType;
 	int m_ConsoleState;
@@ -77,6 +78,7 @@ class CGameConsole : public CComponent
 	static void ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData);
+	static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 public:
 	enum