about summary refs log tree commit diff
path: root/src/engine/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/shared')
-rw-r--r--src/engine/shared/config.cpp7
-rw-r--r--src/engine/shared/config.h3
-rw-r--r--src/engine/shared/config_variables.h20
-rw-r--r--src/engine/shared/console.cpp105
-rw-r--r--src/engine/shared/console.h5
-rw-r--r--src/engine/shared/demo.cpp55
-rw-r--r--src/engine/shared/demo.h5
-rw-r--r--src/engine/shared/econ.cpp38
-rw-r--r--src/engine/shared/econ.h5
-rw-r--r--src/engine/shared/masterserver.cpp8
-rw-r--r--src/engine/shared/netban.cpp603
-rw-r--r--src/engine/shared/netban.h193
-rw-r--r--src/engine/shared/network.h68
-rw-r--r--src/engine/shared/network_console.cpp99
-rw-r--r--src/engine/shared/network_console_conn.cpp2
-rw-r--r--src/engine/shared/network_server.cpp247
-rw-r--r--src/engine/shared/storage.cpp33
17 files changed, 1072 insertions, 424 deletions
diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp
index b9aa1320..d0cb7a6b 100644
--- a/src/engine/shared/config.cpp
+++ b/src/engine/shared/config.cpp
@@ -111,13 +111,8 @@ public:
 	{
 		if(!m_ConfigFile)
 			return;
-#if defined(CONF_FAMILY_WINDOWS)
-		static const char Newline[] = "\r\n";
-#else
-		static const char Newline[] = "\n";
-#endif
 		io_write(m_ConfigFile, pLine, str_length(pLine));
-		io_write(m_ConfigFile, Newline, sizeof(Newline)-1);
+		io_write_newline(m_ConfigFile);
 	}
 };
 
diff --git a/src/engine/shared/config.h b/src/engine/shared/config.h
index ed89daa2..c6d8437f 100644
--- a/src/engine/shared/config.h
+++ b/src/engine/shared/config.h
@@ -20,7 +20,8 @@ enum
 	CFGFLAG_CLIENT=2,
 	CFGFLAG_SERVER=4,
 	CFGFLAG_STORE=8,
-	CFGFLAG_MASTER=16
+	CFGFLAG_MASTER=16,
+	CFGFLAG_ECON=32,
 };
 
 #endif
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index de9a558f..76e4ccd3 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -17,6 +17,7 @@ MACRO_CONFIG_INT(ConsoleOutputLevel, console_output_level, 0, 0, 2, CFGFLAG_CLIE
 
 MACRO_CONFIG_INT(ClCpuThrottle, cl_cpu_throttle, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
 MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(ClLoadCountryFlags, cl_load_country_flags, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Load and show country flags")
 
 MACRO_CONFIG_INT(ClAutoDemoRecord, cl_auto_demo_record, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Automatically record demos")
 MACRO_CONFIG_INT(ClAutoDemoMax, cl_auto_demo_max, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Maximum number of automatically recorded demos (0 = no limit)")
@@ -47,7 +48,7 @@ MACRO_CONFIG_INT(BrSort, br_sort, 0, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
 MACRO_CONFIG_INT(BrSortOrder, br_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
 MACRO_CONFIG_INT(BrMaxRequests, br_max_requests, 25, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser")
 
-MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size")
+MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 128, 32768, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size")
 MACRO_CONFIG_INT(SndRate, snd_rate, 48000, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate")
 MACRO_CONFIG_INT(SndEnable, snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound enable")
 MACRO_CONFIG_INT(SndMusic, snd_enable_music, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Play background music")
@@ -70,11 +71,14 @@ MACRO_CONFIG_INT(GfxTextureQuality, gfx_texture_quality, 1, 0, 1, CFGFLAG_SAVE|C
 MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE|CFGFLAG_CLIENT, "FSAA Samples")
 MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen refresh rate")
 MACRO_CONFIG_INT(GfxFinish, gfx_finish, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(GfxAsyncRender, gfx_asyncrender, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Do rendering async from the the update")
+
+MACRO_CONFIG_INT(GfxThreaded, gfx_threaded, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use the threaded graphics backend")
 
 MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity")
 
 MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server name")
-MACRO_CONFIG_STR(SvBindaddr, sv_bindaddr, 128, "", CFGFLAG_SERVER, "Address to bind the server to")
+MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT|CFGFLAG_SERVER|CFGFLAG_MASTER, "Address to bind the client/server to")
 MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server")
 MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers")
 MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server")
@@ -89,12 +93,12 @@ MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "Th
 MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos")
 MACRO_CONFIG_INT(SvAutoDemoMax, sv_auto_demo_max, 10, 0, 1000, CFGFLAG_SERVER, "Maximum number of automatically recorded demos (0 = no limit)")
 
-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(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if econ authentication fails. 0 just closes the connection")
-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_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_ECON, "Address to bind the external console to. Anything but 'localhost' is dangerous")
+MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_ECON, "Port to use for the external console")
+MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_ECON, "External console password")
+MACRO_CONFIG_INT(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_ECON, "The time a client gets banned if econ authentication fails. 0 just closes the connection")
+MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_ECON, "Time in seconds before the the econ authentification times out")
+MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_ECON, "Adjusts the amount of information in the external console")
 
 MACRO_CONFIG_INT(SvGlobalBantime, sv_global_bantime, 60, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if the ban server reports it. 0 to disable")
 
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
index e4cb1991..443c5904 100644
--- a/src/engine/shared/console.cpp
+++ b/src/engine/shared/console.cpp
@@ -12,6 +12,8 @@
 #include "console.h"
 #include "linereader.h"
 
+// todo: rework this
+
 const char *CConsole::CResult::GetString(unsigned Index)
 {
 	if (Index < 0 || Index >= m_NumArgs)
@@ -374,6 +376,14 @@ void CConsole::ExecuteLine(const char *pStr)
 	CConsole::ExecuteLineStroked(0, pStr); // then release it
 }
 
+void CConsole::ExecuteLineFlag(const char *pStr, int FlagMask)
+{
+	int Temp = m_FlagMask;
+	m_FlagMask = FlagMask;
+	ExecuteLine(pStr);
+	m_FlagMask = Temp;
+}
+
 
 void CConsole::ExecuteFile(const char *pFilename)
 {
@@ -564,6 +574,75 @@ static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData)
 	}
 }
 
+void CConsole::ConToggle(IConsole::IResult *pResult, void *pUser)
+{
+	CConsole* pConsole = static_cast<CConsole *>(pUser);
+	char aBuf[128] = {0};
+	CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), pConsole->m_FlagMask);
+	if(pCommand)
+	{
+		FCommandCallback pfnCallback = pCommand->m_pfnCallback;
+		void *pUserData = pCommand->m_pUserData;
+
+		// check for chain
+		if(pCommand->m_pfnCallback == Con_Chain)
+		{
+			CChain *pChainInfo = static_cast<CChain *>(pCommand->m_pUserData);
+			pfnCallback = pChainInfo->m_pfnCallback;
+			pUserData = pChainInfo->m_pCallbackUserData;
+		}
+
+		if(pfnCallback == IntVariableCommand)
+		{
+			CIntVariableData *pData = static_cast<CIntVariableData *>(pUserData);
+			int Val = *(pData->m_pVariable)==pResult->GetInteger(1) ? pResult->GetInteger(2) : pResult->GetInteger(1);
+			str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(0), Val);
+			pConsole->ExecuteLine(aBuf);
+			aBuf[0] = 0;
+		}
+		else
+			str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pResult->GetString(0));
+	}
+	else
+		str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(0));
+
+	if(aBuf[0] != 0)
+		pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf);
+}
+
+void CConsole::ConToggleStroke(IConsole::IResult *pResult, void *pUser)
+{
+	CConsole* pConsole = static_cast<CConsole *>(pUser);
+	char aBuf[128] = {0};
+	CCommand *pCommand = pConsole->FindCommand(pResult->GetString(1), pConsole->m_FlagMask);
+	if(pCommand)
+	{
+		FCommandCallback pfnCallback = pCommand->m_pfnCallback;
+
+		// check for chain
+		if(pCommand->m_pfnCallback == Con_Chain)
+		{
+			CChain *pChainInfo = static_cast<CChain *>(pCommand->m_pUserData);
+			pfnCallback = pChainInfo->m_pfnCallback;
+		}
+
+		if(pfnCallback == IntVariableCommand)
+		{
+			int Val = pResult->GetInteger(0)==0 ? pResult->GetInteger(3) : pResult->GetInteger(2);
+			str_format(aBuf, sizeof(aBuf), "%s %i", pResult->GetString(1), Val);
+			pConsole->ExecuteLine(aBuf);
+			aBuf[0] = 0;
+		}
+		else
+			str_format(aBuf, sizeof(aBuf), "Invalid command: '%s'.", pResult->GetString(1));
+	}
+	else
+		str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(1));
+
+	if(aBuf[0] != 0)
+		pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf);
+}
+
 CConsole::CConsole(int FlagMask)
 {
 	m_FlagMask = FlagMask;
@@ -585,6 +664,9 @@ CConsole::CConsole(int FlagMask)
 	Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text");
 	Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file");
 
+	Register("toggle", "sii", CFGFLAG_SERVER|CFGFLAG_CLIENT, ConToggle, this, "Toggle config value");
+	Register("+toggle", "sii", CFGFLAG_CLIENT, ConToggleStroke, this, "Toggle config value via keypress");
+
 	Register("mod_command", "s?i", CFGFLAG_SERVER, ConModCommandAccess, this, "Specify command accessibility for moderators");
 	Register("mod_status", "", CFGFLAG_SERVER, ConModCommandStatus, this, "List all commands which are accessible for moderators");
 
@@ -633,7 +715,7 @@ void CConsole::ParseArguments(int NumArgs, const char **ppArguments)
 
 void CConsole::AddCommandSorted(CCommand *pCommand)
 {
-	if(!m_pFirstCommand || str_comp(pCommand->m_pName, m_pFirstCommand->m_pName) < 0)
+	if(!m_pFirstCommand || str_comp(pCommand->m_pName, m_pFirstCommand->m_pName) <= 0)
 	{
 		if(m_pFirstCommand && m_pFirstCommand->m_pNext)
 			pCommand->m_pNext = m_pFirstCommand;
@@ -645,7 +727,7 @@ void CConsole::AddCommandSorted(CCommand *pCommand)
 	{
 		for(CCommand *p = m_pFirstCommand; p; p = p->m_pNext)
 		{
-			if(!p->m_pNext || str_comp(pCommand->m_pName, p->m_pNext->m_pName) < 0)
+			if(!p->m_pNext || str_comp(pCommand->m_pName, p->m_pNext->m_pName) <= 0)
 			{
 				pCommand->m_pNext = p->m_pNext;
 				p->m_pNext = pCommand;
@@ -658,18 +740,25 @@ void CConsole::AddCommandSorted(CCommand *pCommand)
 void CConsole::Register(const char *pName, const char *pParams,
 	int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp)
 {
-	CCommand *pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand;
+	CCommand *pCommand = FindCommand(pName, Flags);
+	bool DoAdd = false;
+	if(pCommand == 0)
+	{
+		pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand;
+		DoAdd = true;
+	}
 	pCommand->m_pfnCallback = pfnFunc;
 	pCommand->m_pUserData = pUser;
 
 	pCommand->m_pName = pName;
 	pCommand->m_pHelp = pHelp;
 	pCommand->m_pParams = pParams;
-	
+
 	pCommand->m_Flags = Flags;
 	pCommand->m_Temp = false;
 
-	AddCommandSorted(pCommand);
+	if(DoAdd)
+		AddCommandSorted(pCommand);
 }
 
 void CConsole::RegisterTemp(const char *pName, const char *pParams,	int Flags, const char *pHelp)
@@ -699,7 +788,7 @@ void CConsole::RegisterTemp(const char *pName, const char *pParams,	int Flags, c
 	}
 
 	pCommand->m_pfnCallback = 0;
-	pCommand->m_pUserData = 0;	
+	pCommand->m_pUserData = 0;
 	pCommand->m_Flags = Flags;
 	pCommand->m_Temp = true;
 
@@ -729,7 +818,7 @@ void CConsole::DeregisterTemp(const char *pName)
 				break;
 			}
 	}
-	
+
 	// add to recycle list
 	if(pRemoved)
 	{
@@ -742,7 +831,7 @@ void CConsole::DeregisterTempAll()
 {
 	// set non temp as first one
 	for(; m_pFirstCommand && m_pFirstCommand->m_Temp; m_pFirstCommand = m_pFirstCommand->m_pNext);
-	
+
 	// remove temp entries from command list
 	for(CCommand *pCommand = m_pFirstCommand; pCommand && pCommand->m_pNext; pCommand = pCommand->m_pNext)
 	{
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
index 6989c696..bbe267d4 100644
--- a/src/engine/shared/console.h
+++ b/src/engine/shared/console.h
@@ -54,6 +54,8 @@ class CConsole : public IConsole
 	static void Con_Chain(IResult *pResult, void *pUserData);
 	static void Con_Echo(IResult *pResult, void *pUserData);
 	static void Con_Exec(IResult *pResult, void *pUserData);
+	static void ConToggle(IResult *pResult, void *pUser);
+	static void ConToggleStroke(IResult *pResult, void *pUser);
 	static void ConModCommandAccess(IResult *pResult, void *pUser);
 	static void ConModCommandStatus(IConsole::IResult *pResult, void *pUser);
 
@@ -156,7 +158,7 @@ class CConsole : public IConsole
 public:
 	CConsole(int FlagMask);
 
-	virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const;
+	virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int FlagMask) const;
 	virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp);
 	virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser);
 
@@ -170,6 +172,7 @@ public:
 
 	virtual bool LineIsValid(const char *pStr);
 	virtual void ExecuteLine(const char *pStr);
+	virtual void ExecuteLineFlag(const char *pStr, int FlagMask);
 	virtual void ExecuteFile(const char *pFilename);
 
 	virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData);
diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp
index 51dd51d9..37c82cce 100644
--- a/src/engine/shared/demo.cpp
+++ b/src/engine/shared/demo.cpp
@@ -13,8 +13,9 @@
 #include "snapshot.h"
 
 static const unsigned char gs_aHeaderMarker[7] = {'T', 'W', 'D', 'E', 'M', 'O', 0};
-static const unsigned char gs_ActVersion = 3;
+static const unsigned char gs_ActVersion = 4;
 static const int gs_LengthOffset = 152;
+static const int gs_NumMarkersOffset = 176;
 
 
 CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta)
@@ -89,6 +90,8 @@ int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, con
 	str_copy(Header.m_aType, pType, sizeof(Header.m_aType));
 	// Header.m_Length - add this on stop
 	str_timestamp(Header.m_aTimestamp, sizeof(Header.m_aTimestamp));
+	// Header.m_aNumTimelineMarkers - add this on stop
+	// Header.m_aTimelineMarkers - add this on stop
 	io_write(DemoFile, &Header, sizeof(Header));
 
 	// write map data
@@ -105,6 +108,7 @@ int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, con
 	m_LastKeyFrame = -1;
 	m_LastTickMarker = -1;
 	m_FirstTick = -1;
+	m_NumTimelineMarkers = 0;
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "Recording to '%s'", pFilename);
@@ -266,6 +270,25 @@ int CDemoRecorder::Stop()
 	aLength[3] = (DemoLength)&0xff;
 	io_write(m_File, aLength, sizeof(aLength));
 
+	// add the timeline markers to the header
+	io_seek(m_File, gs_NumMarkersOffset, IOSEEK_START);
+	char aNumMarkers[4];
+	aNumMarkers[0] = (m_NumTimelineMarkers>>24)&0xff;
+	aNumMarkers[1] = (m_NumTimelineMarkers>>16)&0xff;
+	aNumMarkers[2] = (m_NumTimelineMarkers>>8)&0xff;
+	aNumMarkers[3] = (m_NumTimelineMarkers)&0xff;
+	io_write(m_File, aNumMarkers, sizeof(aNumMarkers));
+	for(int i = 0; i < m_NumTimelineMarkers; i++)
+	{
+		int Marker = m_aTimelineMarkers[i];
+		char aMarker[4];
+		aMarker[0] = (Marker>>24)&0xff;
+		aMarker[1] = (Marker>>16)&0xff;
+		aMarker[2] = (Marker>>8)&0xff;
+		aMarker[3] = (Marker)&0xff;
+		io_write(m_File, aMarker, sizeof(aMarker));
+	}
+
 	io_close(m_File);
 	m_File = 0;
 	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording");
@@ -273,6 +296,24 @@ int CDemoRecorder::Stop()
 	return 0;
 }
 
+void CDemoRecorder::AddDemoMarker()
+{
+	if(m_LastTickMarker < 0 || m_NumTimelineMarkers >= MAX_TIMELINE_MARKERS)
+		return;
+
+	// not more than 1 marker in a second
+	if(m_NumTimelineMarkers > 0)
+	{
+		int Diff = m_LastTickMarker - m_aTimelineMarkers[m_NumTimelineMarkers-1];
+		if(Diff < SERVER_TICK_SPEED*1.0f)
+			return;
+	}
+
+	m_aTimelineMarkers[m_NumTimelineMarkers++] = m_LastTickMarker;
+
+	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Added timeline marker");
+}
+
 
 
 CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta)
@@ -622,6 +663,16 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
 		mem_free(pMapData);
 	}
 
+	// get timeline markers
+	int Num = ((m_Info.m_Header.m_aNumTimelineMarkers[0]<<24)&0xFF000000) | ((m_Info.m_Header.m_aNumTimelineMarkers[1]<<16)&0xFF0000) |
+				((m_Info.m_Header.m_aNumTimelineMarkers[2]<<8)&0xFF00) | (m_Info.m_Header.m_aNumTimelineMarkers[3]&0xFF);
+	m_Info.m_Info.m_NumTimelineMarkers = Num;
+	for(int i = 0; i < Num && i < MAX_TIMELINE_MARKERS; i++)
+	{
+		char *pTimelineMarker = m_Info.m_Header.m_aTimelineMarkers[i];
+		m_Info.m_Info.m_aTimelineMarkers[i] = ((pTimelineMarker[0]<<24)&0xFF000000) | ((pTimelineMarker[1]<<16)&0xFF0000) |
+												((pTimelineMarker[2]<<8)&0xFF00) | (pTimelineMarker[3]&0xFF);
+	}
 
 	// scan the file for interessting points
 	ScanFile();
@@ -775,7 +826,7 @@ void CDemoPlayer::GetDemoName(char *pBuffer, int BufferSize) const
 		else if(*pFileName == '.')
 			pEnd = pFileName;
 	}
-	
+
 	int Length = pEnd > pExtractedName ? min(BufferSize, (int)(pEnd-pExtractedName+1)) : BufferSize;
 	str_copy(pBuffer, pExtractedName, Length);
 }
diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h
index f4ac5685..760e7256 100644
--- a/src/engine/shared/demo.h
+++ b/src/engine/shared/demo.h
@@ -17,6 +17,8 @@ class CDemoRecorder : public IDemoRecorder
 	int m_FirstTick;
 	unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE];
 	class CSnapshotDelta *m_pSnapshotDelta;
+	int m_NumTimelineMarkers;
+	int m_aTimelineMarkers[MAX_TIMELINE_MARKERS];
 
 	void WriteTickMarker(int Tick, int Keyframe);
 	void Write(int Type, const void *pData, int Size);
@@ -25,6 +27,7 @@ public:
 
 	int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, unsigned MapCrc, const char *pType);
 	int Stop();
+	void AddDemoMarker();
 
 	void RecordSnapshot(int Tick, const void *pData, int Size);
 	void RecordMessage(const void *pData, int Size);
@@ -108,7 +111,7 @@ public:
 	void Unpause();
 	int Stop();
 	void SetSpeed(float Speed);
-	int SetPos(float Precent);
+	int SetPos(float Percent);
 	const CInfo *BaseInfo() const { return &m_Info.m_Info; }
 	void GetDemoName(char *pBuffer, int BufferSize) const;
 	bool GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader) const;
diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp
index 617cdbd6..eb7df872 100644
--- a/src/engine/shared/econ.cpp
+++ b/src/engine/shared/econ.cpp
@@ -2,14 +2,15 @@
 #include <engine/shared/config.h>
 
 #include "econ.h"
+#include "netban.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));
+	net_addr_str(pThis->m_NetConsole.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 	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);
@@ -26,9 +27,8 @@ 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));
+	net_addr_str(pThis->m_NetConsole.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 	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);
@@ -52,7 +52,15 @@ void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUse
 	}
 }
 
-void CEcon::Init(IConsole *pConsole)
+void CEcon::ConLogout(IConsole::IResult *pResult, void *pUserData)
+{
+	CEcon *pThis = static_cast<CEcon *>(pUserData);
+
+	if(pThis->m_UserClientID >= 0 && pThis->m_UserClientID < NET_MAX_CONSOLE_CLIENTS && pThis->m_aClients[pThis->m_UserClientID].m_State != CClient::STATE_EMPTY)
+		pThis->m_NetConsole.Drop(pThis->m_UserClientID, "Logout");
+}
+
+void CEcon::Init(IConsole *pConsole, CNetBan *pNetBan)
 {
 	m_pConsole = pConsole;
 
@@ -60,6 +68,7 @@ void CEcon::Init(IConsole *pConsole)
 		m_aClients[i].m_State = CClient::STATE_EMPTY;
 
 	m_Ready = false;
+	m_UserClientID = -1;
 
 	if(g_Config.m_EcPort == 0 || g_Config.m_EcPassword[0] == 0)
 		return;
@@ -74,7 +83,7 @@ void CEcon::Init(IConsole *pConsole)
 		BindAddr.port = g_Config.m_EcPort;
 	}
 
-	if(m_NetConsole.Open(BindAddr, 0))
+	if(m_NetConsole.Open(BindAddr, pNetBan, 0))
 	{
 		m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this);
 		m_Ready = true;
@@ -84,6 +93,8 @@ void CEcon::Init(IConsole *pConsole)
 
 		Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this);
 		m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this);
+
+		Console()->Register("logout", "", CFGFLAG_ECON, ConLogout, this, "Logout of econ");
 	}
 	else
 		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", "couldn't open socket. port might already be in use");
@@ -115,27 +126,26 @@ void CEcon::Update()
 			else
 			{
 				m_aClients[ClientID].m_AuthTries++;
-				char aBuf[128];
-				str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, MAX_AUTH_TRIES);
-				m_NetConsole.Send(ClientID, aBuf);
+				char aMsg[128];
+				str_format(aMsg, sizeof(aMsg), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, MAX_AUTH_TRIES);
+				m_NetConsole.Send(ClientID, aMsg);
 				if(m_aClients[ClientID].m_AuthTries >= MAX_AUTH_TRIES)
 				{
 					if(!g_Config.m_EcBantime)
 						m_NetConsole.Drop(ClientID, "Too many authentication tries");
 					else
-					{
-						NETADDR Addr = m_NetConsole.ClientAddr(ClientID);
-						m_NetConsole.AddBan(Addr, g_Config.m_EcBantime*60);
-					}
+						m_NetConsole.NetBan()->BanAddr(m_NetConsole.ClientAddr(ClientID), g_Config.m_EcBantime*60, "Too many authentication tries");
 				}
 			}
 		}
 		else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
 		{
 			char aFormatted[256];
-			str_format(aFormatted, sizeof(aBuf), "cid=%d cmd='%s'", ClientID, aBuf);
+			str_format(aFormatted, sizeof(aFormatted), "cid=%d cmd='%s'", ClientID, aBuf);
 			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted);
+			m_UserClientID = ClientID;
 			Console()->ExecuteLine(aBuf);
+			m_UserClientID = -1;
 		}
 	}
 
diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h
index daec34c4..197c7a00 100644
--- a/src/engine/shared/econ.h
+++ b/src/engine/shared/econ.h
@@ -3,6 +3,7 @@
 
 #include "network.h"
 
+
 class CEcon
 {
 	enum
@@ -31,9 +32,11 @@ class CEcon
 
 	bool m_Ready;
 	int m_PrintCBIndex;
+	int m_UserClientID;
 
 	static void SendLineCB(const char *pLine, void *pUserData);
 	static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+	static void ConLogout(IConsole::IResult *pResult, void *pUserData);
 
 	static int NewClientCallback(int ClientID, void *pUser);
 	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
@@ -41,7 +44,7 @@ class CEcon
 public:
 	IConsole *Console() { return m_pConsole; }
 
-	void Init(IConsole *pConsole);
+	void Init(IConsole *pConsole, class CNetBan *pNetBan);
 	void Update();
 	void Send(int ClientID, const char *pLine);
 	void Shutdown();
diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp
index 1271eeaf..95482639 100644
--- a/src/engine/shared/masterserver.cpp
+++ b/src/engine/shared/masterserver.cpp
@@ -157,7 +157,7 @@ public:
 						Added = true;
 						break;
 					}
-				
+
 				if(!Added)
 				{
 					for(int i = 0; i < MAX_MASTERSERVERS; ++i)
@@ -192,13 +192,13 @@ public:
 		{
 			char aAddrStr[NETADDR_MAXSTRSIZE];
 			if(m_aMasterServers[i].m_Addr.type != NETTYPE_INVALID)
-				net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr), true);
 			else
 				aAddrStr[0] = 0;
 			char aBuf[256];
-			str_format(aBuf, sizeof(aBuf), "%s %s\n", m_aMasterServers[i].m_aHostname, aAddrStr);
-
+			str_format(aBuf, sizeof(aBuf), "%s %s", m_aMasterServers[i].m_aHostname, aAddrStr);
 			io_write(File, aBuf, str_length(aBuf));
+			io_write_newline(File);
 		}
 
 		io_close(File);
diff --git a/src/engine/shared/netban.cpp b/src/engine/shared/netban.cpp
new file mode 100644
index 00000000..d26d64d4
--- /dev/null
+++ b/src/engine/shared/netban.cpp
@@ -0,0 +1,603 @@
+#include <base/math.h>
+
+#include <engine/console.h>
+#include <engine/storage.h>
+#include <engine/shared/config.h>
+
+#include "netban.h"
+
+
+bool CNetBan::StrAllnum(const char *pStr)
+{
+	while(*pStr)
+	{
+		if(!(*pStr >= '0' && *pStr <= '9'))
+			return false;
+		pStr++;
+	}
+	return true;
+}
+
+
+CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
+{
+	if(pAddr->type==NETTYPE_IPV4)
+		m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
+	else
+		m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+
+			pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF;
+	m_HashIndex = 0;
+}
+
+CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
+{
+	m_Hash = 0;
+	m_HashIndex = 0;
+	for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
+	{
+		m_Hash += pRange->m_LB.ip[i];
+		++m_HashIndex;
+	}
+	m_Hash &= 0xFF;
+}
+
+int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
+{
+	int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16;
+	aHash[0].m_Hash = 0;
+	aHash[0].m_HashIndex = 0;
+	for(int i = 1, Sum = 0; i <= Length; ++i)
+	{
+		Sum += pAddr->ip[i-1];
+		aHash[i].m_Hash = Sum&0xFF;
+		aHash[i].m_HashIndex = i%Length;
+	}
+	return Length;
+}
+
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo,  const CNetHash *pNetHash)
+{
+	if(!m_pFirstFree)
+		return 0;
+
+	// create new ban
+	CBan<T> *pBan = m_pFirstFree;
+	pBan->m_Data = *pData;
+	pBan->m_Info = *pInfo;
+	pBan->m_NetHash = *pNetHash;
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstFree = pBan->m_pNext;
+
+	// add it to the hash list
+	if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
+		m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
+	pBan->m_pHashPrev = 0;
+	pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
+	m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+
+	// update ban count
+	++m_CountUsed;
+
+	return pBan;
+}
+
+template<class T, int HashCount>
+int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan)
+{
+	if(pBan == 0)
+		return -1;
+
+	// remove from hash list
+	if(pBan->m_pHashNext)
+		pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
+	if(pBan->m_pHashPrev)
+		pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
+	else
+		m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
+	pBan->m_pHashNext = pBan->m_pHashPrev = 0;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// add to recycle list
+	if(m_pFirstFree)
+		m_pFirstFree->m_pPrev = pBan;
+	pBan->m_pPrev = 0;
+	pBan->m_pNext = m_pFirstFree;
+	m_pFirstFree = pBan;
+
+	// update ban count
+	--m_CountUsed;
+
+	return 0;
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo)
+{
+	pBan->m_Info = *pInfo;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Reset()
+{
+	mem_zero(m_paaHashList, sizeof(m_paaHashList));
+	mem_zero(m_aBans, sizeof(m_aBans));
+	m_pFirstUsed = 0;
+	m_CountUsed = 0;
+
+	for(int i = 1; i < MAX_BANS-1; ++i)
+	{
+		m_aBans[i].m_pNext = &m_aBans[i+1];
+		m_aBans[i].m_pPrev = &m_aBans[i-1];
+	}
+
+	m_aBans[0].m_pNext = &m_aBans[1];
+	m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2];
+	m_pFirstFree = &m_aBans[0];
+}
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const
+{
+	if(Index < 0 || Index >= Num())
+		return 0;
+
+	for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
+	{
+		if(Index == 0)
+			return pBan;
+	}
+
+	return 0;
+}
+
+
+template<class T>
+void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const
+{
+	if(pBan == 0)
+	{
+		if(BuffSize > 0)
+			pBuf[0] = 0;
+		return;
+	}
+	
+	// build type based part
+	char aBuf[256];
+	if(Type == MSGTYPE_PLAYER)
+		str_copy(aBuf, "You have been banned", sizeof(aBuf));
+	else
+	{
+		char aTemp[256];
+		switch(Type)
+		{
+		case MSGTYPE_LIST:
+			str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANADD:
+			str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANREM:
+			str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		default:
+			aBuf[0] = 0;
+		}
+	}
+
+	// add info part
+	if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
+	{
+		int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60;
+		if(Mins <= 1)
+			str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
+		else
+			str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
+	}
+	else
+		str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason);
+}
+
+template<class T>
+int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
+{
+	// do not ban localhost
+	if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6))
+	{
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (localhost)");
+		return -1;
+	}
+
+	int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER;
+
+	// set up info
+	CBanInfo Info = {0};
+	Info.m_Expires = Stamp;
+	str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason));
+
+	// check if it already exists
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		// adjust the ban
+		pBanPool->Update(pBan, &Info);
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 1;
+	}
+
+	// add ban and print result
+	pBan = pBanPool->Add(pData, &Info, &NetHash);
+	if(pBan)
+	{
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 0;
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (full banlist)");
+	return -1;
+}
+
+template<class T>
+int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
+{
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		char aBuf[256];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
+		pBanPool->Remove(pBan);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 0;
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban failed (invalid entry)");
+	return -1;
+}
+
+void CNetBan::Init(IConsole *pConsole, IStorage *pStorage)
+{
+	m_pConsole = pConsole;
+	m_pStorage = pStorage;
+	m_BanAddrPool.Reset();
+	m_BanRangePool.Reset();
+
+	net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4);
+	net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6);
+
+	Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBan, this, "Ban ip for x minutes for any reason");
+	Console()->Register("ban_range", "ss?ir", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBanRange, this, "Ban ip range for x minutes for any reason");
+	Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnban, this, "Unban ip/banlist entry");
+	Console()->Register("unban_range", "ss", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnbanRange, this, "Unban ip range");
+	Console()->Register("unban_all", "", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnbanAll, this, "Unban all entries");
+	Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBans, this, "Show banlist");
+	Console()->Register("bans_save", "s", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBansSave, this, "Save banlist in a file");
+}
+
+void CNetBan::Update()
+{
+	int Now = time_timestamp();
+
+	// remove expired bans
+	char aBuf[256], aNetStr[256];
+	while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		m_BanAddrPool.Remove(m_BanAddrPool.First());
+	}
+	while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		m_BanRangePool.Remove(m_BanRangePool.First());
+	}
+}
+
+int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
+{
+	return Ban(&m_BanAddrPool, pAddr, Seconds, pReason);
+}
+
+int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
+{
+	if(pRange->IsValid())
+		return Ban(&m_BanRangePool, pRange, Seconds, pReason);
+
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByAddr(const NETADDR *pAddr)
+{
+	return Unban(&m_BanAddrPool, pAddr);
+}
+
+int CNetBan::UnbanByRange(const CNetRange *pRange)
+{
+	if(pRange->IsValid())
+		return Unban(&m_BanRangePool, pRange);
+	
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByIndex(int Index)
+{
+	int Result;
+	char aBuf[256];
+	CBanAddr *pBan = m_BanAddrPool.Get(Index);
+	if(pBan)
+	{
+		NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+		Result = m_BanAddrPool.Remove(pBan);
+	}
+	else
+	{
+		CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num());
+		if(pBan)
+		{
+			NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+			Result = m_BanRangePool.Remove(pBan);
+		}
+		else
+		{
+			Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban failed (invalid index)");
+			return -1;
+		}
+	}
+
+	char aMsg[256];
+	str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf);
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	return Result;
+}
+
+bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const
+{
+	CNetHash aHash[17];
+	int Length = CNetHash::MakeHashArray(pAddr, aHash);
+
+	// check ban adresses
+	CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]);
+	if(pBan)
+	{
+		MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+		return true;
+	}
+
+	// check ban ranges
+	for(int i = Length-1; i >= 0; --i)
+	{
+		for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext)
+		{
+			if(NetMatch(&pBan->m_Data, pAddr, i, Length))
+			{
+				MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}
+
+void CNetBan::ConBan(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr = pResult->GetString(0);
+	int Minutes = pResult->NumArguments()>1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30;
+	const char *pReason = pResult->NumArguments()>2 ? pResult->GetString(2) : "No reason given";
+
+	NETADDR Addr;
+	if(net_addr_from_str(&Addr, pStr) == 0)
+		pThis->BanAddr(&Addr, Minutes*60, pReason);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid network address)");
+}
+
+void CNetBan::ConBanRange(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr1 = pResult->GetString(0);
+	const char *pStr2 = pResult->GetString(1);
+	int Minutes = pResult->NumArguments()>2 ? clamp(pResult->GetInteger(2), 0, 44640) : 30;
+	const char *pReason = pResult->NumArguments()>3 ? pResult->GetString(3) : "No reason given";
+
+	CNetRange Range;
+	if(net_addr_from_str(&Range.m_LB, pStr1) == 0 && net_addr_from_str(&Range.m_UB, pStr2) == 0)
+		pThis->BanRange(&Range, Minutes*60, pReason);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid range)");
+}
+
+void CNetBan::ConUnban(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr = pResult->GetString(0);
+	if(StrAllnum(pStr))
+		pThis->UnbanByIndex(str_toint(pStr));
+	else
+	{
+		NETADDR Addr;
+		if(net_addr_from_str(&Addr, pStr) == 0)
+			pThis->UnbanByAddr(&Addr);
+		else
+			pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban error (invalid network address)");
+	}
+}
+
+void CNetBan::ConUnbanRange(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr1 = pResult->GetString(0);
+	const char *pStr2 = pResult->GetString(1);
+
+	CNetRange Range;
+	if(net_addr_from_str(&Range.m_LB, pStr1) == 0 && net_addr_from_str(&Range.m_UB, pStr2) == 0)
+		pThis->UnbanByRange(&Range);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban error (invalid range)");
+}
+
+void CNetBan::ConUnbanAll(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	pThis->UnbanAll();
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unbanned all entries");
+}
+
+void CNetBan::ConBans(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	int Count = 0;
+	char aBuf[256], aMsg[256];
+	for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		pThis->MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		str_format(aMsg, sizeof(aMsg), "#%i %s", Count++, aBuf);
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	}
+	for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		pThis->MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		str_format(aMsg, sizeof(aMsg), "#%i %s", Count++, aBuf);
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	}
+	str_format(aMsg, sizeof(aMsg), "%d %s", Count, Count==1?"ban":"bans");
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+}
+
+void CNetBan::ConBansSave(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	char aBuf[256];
+	IOHANDLE File = pThis->Storage()->OpenFile(pResult->GetString(0), IOFLAG_WRITE, IStorage::TYPE_SAVE);
+	if(!File)
+	{
+		str_format(aBuf, sizeof(aBuf), "failed to save banlist to '%s'", pResult->GetString(0));
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return;
+	}
+
+	int Now = time_timestamp();
+	char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
+	for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		int Min = pBan->m_Info.m_Expires>-1 ? (pBan->m_Info.m_Expires-Now+59)/60 : -1;
+		net_addr_str(&pBan->m_Data, aAddrStr1, sizeof(aAddrStr1), false);
+		str_format(aBuf, sizeof(aBuf), "ban %s %i %s", aAddrStr1, Min, pBan->m_Info.m_aReason);
+		io_write(File, aBuf, str_length(aBuf));
+		io_write_newline(File);
+	}
+	for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		int Min = pBan->m_Info.m_Expires>-1 ? (pBan->m_Info.m_Expires-Now+59)/60 : -1;
+		net_addr_str(&pBan->m_Data.m_LB, aAddrStr1, sizeof(aAddrStr1), false);
+		net_addr_str(&pBan->m_Data.m_UB, aAddrStr2, sizeof(aAddrStr2), false);
+		str_format(aBuf, sizeof(aBuf), "ban_range %s %s %i %s", aAddrStr1, aAddrStr2, Min, pBan->m_Info.m_aReason);
+		io_write(File, aBuf, str_length(aBuf));
+		io_write_newline(File);
+	}
+
+	io_close(File);
+	str_format(aBuf, sizeof(aBuf), "saved banlist to '%s'", pResult->GetString(0));
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+}
diff --git a/src/engine/shared/netban.h b/src/engine/shared/netban.h
new file mode 100644
index 00000000..447a838d
--- /dev/null
+++ b/src/engine/shared/netban.h
@@ -0,0 +1,193 @@
+#ifndef ENGINE_SHARED_NETBAN_H
+#define ENGINE_SHARED_NETBAN_H
+
+#include <base/system.h>
+
+
+inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
+{
+	return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20);
+}
+
+class CNetRange
+{
+public:
+	NETADDR m_LB;
+	NETADDR m_UB;
+
+	bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; }
+};
+
+inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
+{
+	return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB);
+}
+
+
+class CNetBan
+{
+protected:
+	bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
+	{
+		return NetComp(pAddr1, pAddr2) == 0;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
+	{
+		return pRange->m_LB.type == pAddr->type &&
+			mem_comp(&pRange->m_LB.ip[Start], &pAddr->ip[Start], Length-Start) <= 0 && mem_comp(&pRange->m_UB.ip[Start], &pAddr->ip[Start], Length-Start) >= 0;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
+	{
+		return NetMatch(pRange, pAddr, 0,  pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16);
+	}
+
+	const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr[NETADDR_MAXSTRSIZE];
+		net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false);
+		str_format(pBuffer, BufferSize, "'%s'", aAddrStr);
+		return pBuffer;
+	}
+
+	const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
+		net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false);
+		net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false);
+		str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2);
+		return pBuffer;
+	}
+
+	// todo: move?
+	static bool StrAllnum(const char *pStr);
+
+	class CNetHash
+	{
+	public:
+		int m_Hash;
+		int m_HashIndex;	// matching parts for ranges, 0 for addr
+
+		CNetHash() {}	
+		CNetHash(const NETADDR *pAddr);
+		CNetHash(const CNetRange *pRange);
+
+		static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
+	};
+
+	struct CBanInfo
+	{
+		enum
+		{
+			EXPIRES_NEVER=-1,
+			REASON_LENGTH=64,
+		};
+		int m_Expires;
+		char m_aReason[REASON_LENGTH];
+	};
+
+	template<class T> struct CBan
+	{
+		T m_Data;
+		CBanInfo m_Info;
+		CNetHash m_NetHash;
+
+		// hash list
+		CBan *m_pHashNext;
+		CBan *m_pHashPrev;
+
+		// used or free list
+		CBan *m_pNext;
+		CBan *m_pPrev;
+	};
+
+	template<class T, int HashCount> class CBanPool
+	{
+	public:
+		typedef T CDataType;
+
+		CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
+		int Remove(CBan<CDataType> *pBan);
+		void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo);
+		void Reset();
+	
+		int Num() const { return m_CountUsed; }
+		bool IsFull() const { return m_CountUsed == MAX_BANS; }
+
+		CBan<CDataType> *First() const { return m_pFirstUsed; }
+		CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
+		CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const
+		{
+			for(CBan<CDataType> *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
+			{
+				if(NetComp(&pBan->m_Data, pData) == 0)
+					return pBan;
+			}
+
+			return 0;
+		}
+		CBan<CDataType> *Get(int Index) const;
+
+	private:
+		enum
+		{
+			MAX_BANS=1024,
+		};
+
+		CBan<CDataType> *m_paaHashList[HashCount][256];
+		CBan<CDataType> m_aBans[MAX_BANS];
+		CBan<CDataType> *m_pFirstFree;
+		CBan<CDataType> *m_pFirstUsed;
+		int m_CountUsed;
+	};
+
+	typedef CBanPool<NETADDR, 1> CBanAddrPool;
+	typedef CBanPool<CNetRange, 16> CBanRangePool;
+	typedef CBan<NETADDR> CBanAddr;
+	typedef CBan<CNetRange> CBanRange;
+	
+	template<class T> void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const;
+	template<class T> int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
+	template<class T> int Unban(T *pBanPool, const typename T::CDataType *pData);
+
+	class IConsole *m_pConsole;
+	class IStorage *m_pStorage;
+	CBanAddrPool m_BanAddrPool;
+	CBanRangePool m_BanRangePool;
+	NETADDR m_LocalhostIPV4, m_LocalhostIPV6;
+
+public:
+	enum
+	{
+		MSGTYPE_PLAYER=0,
+		MSGTYPE_LIST,
+		MSGTYPE_BANADD,
+		MSGTYPE_BANREM,
+	};
+
+	class IConsole *Console() const { return m_pConsole; }
+	class IStorage *Storage() const { return m_pStorage; }
+
+	virtual ~CNetBan() {}
+	virtual void Init(class IConsole *pConsole, class IStorage *pStorage);
+	void Update();
+
+	virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
+	virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
+	int UnbanByAddr(const NETADDR *pAddr);
+	int UnbanByRange(const CNetRange *pRange);
+	int UnbanByIndex(int Index);
+	void UnbanAll() { m_BanAddrPool.Reset(); m_BanRangePool.Reset(); }
+	bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const;
+
+	static void ConBan(class IConsole::IResult *pResult, void *pUser);
+	static void ConBanRange(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnban(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnbanRange(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnbanAll(class IConsole::IResult *pResult, void *pUser);
+	static void ConBans(class IConsole::IResult *pResult, void *pUser);
+	static void ConBansSave(class IConsole::IResult *pResult, void *pUser);
+};
+
+#endif
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index ca6ca02a..cf2310bb 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -73,8 +73,6 @@ enum
 	NET_CTRLMSG_ACCEPT=3,
 	NET_CTRLMSG_CLOSE=4,
 
-	NET_SERVER_MAXBANS=1024,
-
 	NET_CONN_BUFFERSIZE=1024*32,
 
 	NET_ENUM_TERMINATOR
@@ -182,7 +180,7 @@ public:
 	const char *ErrorString();
 	void SignalResend();
 	int State() const { return m_State; }
-	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const NETADDR *PeerAddress() const { return &m_PeerAddr; }
 
 	void ResetErrorString() { m_ErrorString[0] = 0; }
 	const char *ErrorString() const { return m_ErrorString; }
@@ -214,7 +212,7 @@ public:
 	void Disconnect(const char *pReason);
 
 	int State() const { return m_State; }
-	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const NETADDR *PeerAddress() const { return &m_PeerAddr; }
 	const char *ErrorString() const { return m_aErrorString; }
 
 	void Reset();
@@ -249,6 +247,7 @@ public:
 	{
 		MAX_BANMASTERS=16
 	};
+
 	struct CBanInfo
 	{
 		NETADDR m_Addr;
@@ -263,31 +262,12 @@ private:
 		CNetConnection m_Connection;
 	};
 
-	struct CBan
-	{
-	public:
-		CBanInfo m_Info;
-
-		// hash list
-		CBan *m_pHashNext;
-		CBan *m_pHashPrev;
-
-		// used or free list
-		CBan *m_pNext;
-		CBan *m_pPrev;
-	};
-
-
 	NETSOCKET m_Socket;
+	class CNetBan *m_pNetBan;
 	CSlot m_aSlots[NET_MAX_CLIENTS];
 	int m_MaxClients;
 	int m_MaxClientsPerIP;
 
-	CBan *m_aBans[256];
-	CBan m_BanPool[NET_SERVER_MAXBANS];
-	CBan *m_BanPool_FirstFree;
-	CBan *m_BanPool_FirstUsed;
-
 	NETADDR m_aBanmasters[MAX_BANMASTERS];
 	int m_NumBanmasters;
 	
@@ -297,13 +277,11 @@ private:
 
 	CNetRecvUnpacker m_RecvUnpacker;
 
-	void BanRemoveByObject(CBan *pBan);
-
 public:
 	int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
 
 	//
-	bool Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int Flags);
+	bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags);
 	int Close();
 
 	//
@@ -314,20 +292,17 @@ public:
 	//
 	int Drop(int ClientID, const char *pReason);
 
-	// banning
-	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
-	int BanRemove(NETADDR Addr);
-	int BanNum(); // caution, slow
-	int BanGet(int Index, CBanInfo *pInfo); // caution, slow
-
 	// status requests
-	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
 	NETSOCKET Socket() const { return m_Socket; }
-	int NetType() { return m_Socket.type; }
+	class CNetBan *NetBan() const { return m_pNetBan; }
+	int NetType() const { return m_Socket.type; }
 	int MaxClients() const { return m_MaxClients; }
 
 	//
 	void SetMaxClientsPerIP(int Max);
+
+	//
 	int BanmasterAdd(const char *pAddrStr);
 	int BanmasterNum() const;
 	NETADDR* BanmasterGet(int Index);
@@ -337,27 +312,13 @@ public:
 
 class CNetConsole
 {
-	enum
-	{
-		MAX_BANS=128,
-	};
-
-	int FindBan(NETADDR Addr);
-	void UpdateBans();
-
-	struct CBanEntry
-	{
-		NETADDR m_Addr;
-		int m_Expires;
-	} m_aBans[MAX_BANS];
-	int m_NumBans;
-
 	struct CSlot
 	{
 		CConsoleNetConnection m_Connection;
 	};
 
 	NETSOCKET m_Socket;
+	class CNetBan *m_pNetBan;
 	CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
 
 	NETFUNC_NEWCLIENT m_pfnNewClient;
@@ -370,7 +331,7 @@ public:
 	void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
 
 	//
-	bool Open(NETADDR BindAddr, int Flags);
+	bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int Flags);
 	int Close();
 
 	//
@@ -382,10 +343,9 @@ public:
 	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
 	int Drop(int ClientID, const char *pReason);
 
-	bool AddBan(NETADDR Addr, int Seconds);
-
 	// status requests
-	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	class CNetBan *NetBan() const { return m_pNetBan; }
 };
 
 
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
index 13ed3751..ded83f68 100644
--- a/src/engine/shared/network_console.cpp
+++ b/src/engine/shared/network_console.cpp
@@ -1,15 +1,21 @@
 /* (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 <engine/console.h>
+
+#include "netban.h"
 #include "network.h"
 
-bool CNetConsole::Open(NETADDR BindAddr, int Flags)
+
+bool CNetConsole::Open(NETADDR BindAddr, CNetBan *pNetBan, int Flags)
 {
 	// zero out the whole structure
 	mem_zero(this, sizeof(*this));
 	m_Socket.type = NETTYPE_INVALID;
 	m_Socket.ipv4sock = -1;
 	m_Socket.ipv6sock = -1;
+	m_pNetBan = pNetBan;
 
 	// open socket
 	m_Socket = net_tcp_create(BindAddr);
@@ -56,7 +62,7 @@ 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++)
 	{
@@ -64,8 +70,7 @@ int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
 			FreeSlot = i;
 		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
 		{
-			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
-			if(net_addr_comp(pAddr, &PeerAddr) == 0)
+			if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0)
 			{
 				str_copy(aError, "only one client per IP allowed", sizeof(aError));
 				break;
@@ -99,26 +104,16 @@ int CNetConsole::Update()
 
 	if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
 	{
-		int Index = FindBan(Addr);
-		if(Index == -1)
-			AcceptClient(Socket, &Addr);
-		else
+		// check if we just should drop the packet
+		char aBuf[128];
+		if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
 		{
-			char aBuf[128];
-			if(m_aBans[Index].m_Expires > -1)
-			{
-				int Mins = (m_aBans[Index].m_Expires-time_timestamp()+ 59) / 60;
-				if(Mins <= 1)
-					str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
-				else
-					str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
-			}
-			else
-				str_format(aBuf, sizeof(aBuf), "You have been banned for life");
-			
+			// banned, reply with a message and drop
 			net_tcp_send(Socket, aBuf, str_length(aBuf));
 			net_tcp_close(Socket);
 		}
+		else
+			AcceptClient(Socket, &Addr);
 	}
 
 	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
@@ -129,8 +124,6 @@ int CNetConsole::Update()
 			Drop(i, m_aSlots[i].m_Connection.ErrorString());
 	}
 
-	UpdateBans();
-
 	return 0;
 }
 
@@ -155,65 +148,3 @@ int CNetConsole::Send(int ClientID, const char *pLine)
 	else
 		return -1;
 }
-
-int CNetConsole::FindBan(NETADDR Addr)
-{
-	Addr.port = 0;
-	for(int i = 0; i < m_NumBans; i++)
-		if(net_addr_comp(&m_aBans[i].m_Addr, &Addr) == 0)
-			return i;
-
-	return -1;
-}
-
-bool CNetConsole::AddBan(NETADDR Addr, int Seconds)
-{
-	if(m_NumBans == MAX_BANS)
-		return false;
-	
-	Addr.port = 0;
-	int Index = FindBan(Addr);
-	if(Index == -1)
-	{
-		Index = m_NumBans++;
-		m_aBans[Index].m_Addr = Addr;
-	}
-	m_aBans[Index].m_Expires = Seconds>0 ? time_timestamp()+Seconds : -1;
-
-	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
-	{
-		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
-		{
-			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
-			PeerAddr.port = 0;
-			if(net_addr_comp(&Addr, &PeerAddr) == 0)
-			{
-				char aBuf[128];
-				if(Seconds>0)
-				{
-					int Mins = (Seconds + 59) / 60;
-					if(Mins <= 1)
-						str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
-					else
-						str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
-				}
-				else
-					str_format(aBuf, sizeof(aBuf), "You have been banned for life");
-				Drop(i, aBuf);
-			}
-		}
-	}
-	return true;
-}
-
-void CNetConsole::UpdateBans()
-{
-	int Now = time_timestamp();
-	for(int i = 0; i < m_NumBans; ++i)
-		if(m_aBans[i].m_Expires > 0 && m_aBans[i].m_Expires < Now)
-		{
-			m_aBans[i] = m_aBans[m_NumBans-1];
-			--m_NumBans;
-			break;
-		}
-}
diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp
index 75b581fa..9bc163af 100644
--- a/src/engine/shared/network_console_conn.cpp
+++ b/src/engine/shared/network_console_conn.cpp
@@ -181,6 +181,6 @@ int CConsoleNetConnection::Send(const char *pLine)
 		pData += Send;
 		Length -= Send;
 	}
-	
+
 	return 0;
 }
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index 73e3cf17..e2e0d16b 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -1,33 +1,15 @@
 /* (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 <banmaster/banmaster.h>
-#include "network.h"
 
-#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \
-	{ if(First) First->Prev = Object; \
-	Object->Prev = (struct CBan *)0; \
-	Object->Next = First; \
-	First = Object; }
-
-#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \
-	{ Object->Prev = After; \
-	Object->Next = After->Next; \
-	After->Next = Object; \
-	if(Object->Next) \
-		Object->Next->Prev = Object; \
-	}
+#include <engine/console.h>
+#include <banmaster/banmaster.h>
 
-#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \
-	{ if(Object->Next) Object->Next->Prev = Object->Prev; \
-	if(Object->Prev) Object->Prev->Next = Object->Next; \
-	else First = Object->Next; \
-	Object->Next = 0; Object->Prev = 0; }
+#include "netban.h"
+#include "network.h"
 
-#define MACRO_LIST_FIND(Start, Next, Expression) \
-	{ while(Start && !(Expression)) Start = Start->Next; }
 
-bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int Flags)
+bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags)
 {
 	// zero out the whole structure
 	mem_zero(this, sizeof(*this));
@@ -37,6 +19,8 @@ bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int
 	if(!m_Socket.type)
 		return false;
 
+	m_pNetBan = pNetBan;
+
 	// clamp clients
 	m_MaxClients = MaxClients;
 	if(m_MaxClients > NET_MAX_CLIENTS)
@@ -49,17 +33,6 @@ bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int
 	for(int i = 0; i < NET_MAX_CLIENTS; i++)
 		m_aSlots[i].m_Connection.Init(m_Socket);
 
-	// setup all pointers for bans
-	for(int i = 1; i < NET_SERVER_MAXBANS-1; i++)
-	{
-		m_BanPool[i].m_pNext = &m_BanPool[i+1];
-		m_BanPool[i].m_pPrev = &m_BanPool[i-1];
-	}
-
-	m_BanPool[0].m_pNext = &m_BanPool[1];
-	m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2];
-	m_BanPool_FirstFree = &m_BanPool[0];
-
 	return true;
 }
 
@@ -95,159 +68,8 @@ int CNetServer::Drop(int ClientID, const char *pReason)
 	return 0;
 }
 
-int CNetServer::BanGet(int Index, CBanInfo *pInfo)
-{
-	CBan *pBan;
-	for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--)
-		{}
-
-	if(!pBan)
-		return 0;
-	*pInfo = pBan->m_Info;
-	return 1;
-}
-
-int CNetServer::BanNum()
-{
-	int Count = 0;
-	CBan *pBan;
-	for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext)
-		Count++;
-	return Count;
-}
-
-void CNetServer::BanRemoveByObject(CBan *pBan)
-{
-	int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3]+
-					pBan->m_Info.m_Addr.ip[4]+pBan->m_Info.m_Addr.ip[5]+pBan->m_Info.m_Addr.ip[6]+pBan->m_Info.m_Addr.ip[7]+
-					pBan->m_Info.m_Addr.ip[8]+pBan->m_Info.m_Addr.ip[9]+pBan->m_Info.m_Addr.ip[10]+pBan->m_Info.m_Addr.ip[11]+
-					pBan->m_Info.m_Addr.ip[12]+pBan->m_Info.m_Addr.ip[13]+pBan->m_Info.m_Addr.ip[14]+pBan->m_Info.m_Addr.ip[15])&0xff;
-	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&pBan->m_Info.m_Addr, aAddrStr, sizeof(aAddrStr));
-	dbg_msg("netserver", "removing ban on %s", aAddrStr);
-	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-	MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
-	MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
-}
-
-int CNetServer::BanRemove(NETADDR Addr)
-{
-	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3]+Addr.ip[4]+Addr.ip[5]+Addr.ip[6]+Addr.ip[7]+
-					Addr.ip[8]+Addr.ip[9]+Addr.ip[10]+Addr.ip[11]+Addr.ip[12]+Addr.ip[13]+Addr.ip[14]+Addr.ip[15])&0xff;
-	CBan *pBan = m_aBans[IpHash];
-
-	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
-
-	if(pBan)
-	{
-		BanRemoveByObject(pBan);
-		return 0;
-	}
-
-	return -1;
-}
-
-int CNetServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason)
-{
-	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3]+Addr.ip[4]+Addr.ip[5]+Addr.ip[6]+Addr.ip[7]+
-					Addr.ip[8]+Addr.ip[9]+Addr.ip[10]+Addr.ip[11]+Addr.ip[12]+Addr.ip[13]+Addr.ip[14]+Addr.ip[15])&0xff;
-	int Stamp = -1;
-	CBan *pBan;
-
-	// remove the port
-	Addr.port = 0;
-
-	if(Seconds)
-		Stamp = time_timestamp() + Seconds;
-
-	// search to see if it already exists
-	pBan = m_aBans[IpHash];
-	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
-	if(pBan)
-	{
-		// adjust the ban
-		pBan->m_Info.m_Expires = Stamp;
-		return 0;
-	}
-
-	if(!m_BanPool_FirstFree)
-		return -1;
-
-	// fetch and clear the new ban
-	pBan = m_BanPool_FirstFree;
-	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
-
-	// setup the ban info
-	pBan->m_Info.m_Expires = Stamp;
-	pBan->m_Info.m_Addr = Addr;
-	str_copy(pBan->m_Info.m_Reason, pReason, sizeof(pBan->m_Info.m_Reason));
-
-	// add it to the ban hash
-	MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
-
-	// insert it into the used list
-	{
-		if(m_BanPool_FirstUsed)
-		{
-			CBan *pInsertAfter = m_BanPool_FirstUsed;
-			MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires);
-
-			if(pInsertAfter)
-				pInsertAfter = pInsertAfter->m_pPrev;
-			else
-			{
-				// add to last
-				pInsertAfter = m_BanPool_FirstUsed;
-				while(pInsertAfter->m_pNext)
-					pInsertAfter = pInsertAfter->m_pNext;
-			}
-
-			if(pInsertAfter)
-			{
-				MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext);
-			}
-			else
-			{
-				MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-			}
-		}
-		else
-		{
-			MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-		}
-	}
-
-	// drop banned clients
-	{
-		char Buf[128];
-		NETADDR BanAddr;
-
-		if(Stamp > -1)
-		{
-			int Mins = (Seconds + 59) / 60;
-			if(Mins <= 1)
-				str_format(Buf, sizeof(Buf), "You have been banned for 1 minute (%s)", pReason);
-			else
-				str_format(Buf, sizeof(Buf), "You have been banned for %d minutes (%s)", Mins, pReason);
-		}
-		else
-			str_format(Buf, sizeof(Buf), "You have been banned for life (%s)", pReason);
-
-		for(int i = 0; i < MaxClients(); i++)
-		{
-			BanAddr = m_aSlots[i].m_Connection.PeerAddress();
-			BanAddr.port = 0;
-
-			if(net_addr_comp(&Addr, &BanAddr) == 0)
-				Drop(i, Buf);
-		}
-	}
-	return 0;
-}
-
 int CNetServer::Update()
 {
-	int Now = time_timestamp();
 	for(int i = 0; i < MaxClients(); i++)
 	{
 		m_aSlots[i].m_Connection.Update();
@@ -255,13 +77,6 @@ int CNetServer::Update()
 			Drop(i, m_aSlots[i].m_Connection.ErrorString());
 	}
 
-	// remove expired bans
-	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires > -1 && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
-	{
-		CBan *pBan = m_BanPool_FirstUsed;
-		BanRemoveByObject(pBan);
-	}
-
 	return 0;
 }
 
@@ -270,8 +85,6 @@ int CNetServer::Update()
 */
 int CNetServer::Recv(CNetChunk *pChunk)
 {
-	unsigned Now = time_timestamp();
-
 	while(1)
 	{
 		NETADDR Addr;
@@ -289,36 +102,12 @@ int CNetServer::Recv(CNetChunk *pChunk)
 
 		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
 		{
-			CBan *pBan = 0;
-			NETADDR BanAddr = Addr;
-			int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3]+BanAddr.ip[4]+BanAddr.ip[5]+BanAddr.ip[6]+BanAddr.ip[7]+
-							BanAddr.ip[8]+BanAddr.ip[9]+BanAddr.ip[10]+BanAddr.ip[11]+BanAddr.ip[12]+BanAddr.ip[13]+BanAddr.ip[14]+BanAddr.ip[15])&0xff;
-			int Found = 0;
-			BanAddr.port = 0;
-
-			// search a ban
-			for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext)
-			{
-				if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0)
-					break;
-			}
-
 			// check if we just should drop the packet
-			if(pBan)
+			char aBuf[128];
+			if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
 			{
 				// banned, reply with a message
-				char BanStr[128];
-				if(pBan->m_Info.m_Expires > -1)
-				{
-					int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60;
-					if(Mins <= 1)
-						str_format(BanStr, sizeof(BanStr), "Banned for 1 minute (%s)", pBan->m_Info.m_Reason);
-					else
-						str_format(BanStr, sizeof(BanStr), "Banned for %d minutes (%s)", Mins, pBan->m_Info.m_Reason);
-				}
-				else
-					str_format(BanStr, sizeof(BanStr), "Banned for life (%s)", pBan->m_Info.m_Reason);
-				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1);
+				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1);
 				continue;
 			}
 
@@ -336,16 +125,15 @@ int CNetServer::Recv(CNetChunk *pChunk)
 				// TODO: check size here
 				if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
 				{
-					Found = 0;
+					bool Found = false;
 
 					// check if we already got this client
 					for(int i = 0; i < MaxClients(); i++)
 					{
-						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
 						if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
-							net_addr_comp(&PeerAddr, &Addr) == 0)
+							net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
 						{
-							Found = 1; // silent ignore.. we got this client already
+							Found = true; // silent ignore.. we got this client already
 							break;
 						}
 					}
@@ -356,7 +144,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 						CNetChunk Packet;
 						char aBuffer[sizeof(BANMASTER_IPCHECK) + NETADDR_MAXSTRSIZE];
 						mem_copy(aBuffer, BANMASTER_IPCHECK, sizeof(BANMASTER_IPCHECK));
-						net_addr_str(&Addr, aBuffer + sizeof(BANMASTER_IPCHECK), sizeof(aBuffer) - sizeof(BANMASTER_IPCHECK));
+						net_addr_str(&Addr, aBuffer + sizeof(BANMASTER_IPCHECK), sizeof(aBuffer) - sizeof(BANMASTER_IPCHECK), 0);
 
 						Packet.m_ClientID = -1;
 						Packet.m_Flags = NETSENDFLAG_CONNLESS;
@@ -378,7 +166,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
 								continue;
 
-							OtherAddr = m_aSlots[i].m_Connection.PeerAddress();
+							OtherAddr = *m_aSlots[i].m_Connection.PeerAddress();
 							OtherAddr.port = 0;
 							if(!net_addr_comp(&ThisAddr, &OtherAddr))
 							{
@@ -396,7 +184,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 						{
 							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
 							{
-								Found = 1;
+								Found = true;
 								m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
 								if(m_pfnNewClient)
 									m_pfnNewClient(i, m_UserPtr);
@@ -417,8 +205,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 					// normal packet, find matching slot
 					for(int i = 0; i < MaxClients(); i++)
 					{
-						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
-						if(net_addr_comp(&PeerAddr, &Addr) == 0)
+						if(net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
 						{
 							if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
 							{
diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp
index d6f83dc8..a9ccdc49 100644
--- a/src/engine/shared/storage.cpp
+++ b/src/engine/shared/storage.cpp
@@ -30,7 +30,7 @@ public:
 		m_aUserdir[0] = 0;
 	}
 
-	int Init(const char *pApplicationName, int NumArgs, const char **ppArguments)
+	int Init(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments)
 	{
 		// get userdir
 		fs_storage_path(pApplicationName, m_aUserdir, sizeof(m_aUserdir));
@@ -52,14 +52,17 @@ public:
 		}
 
 		// add save directories
-		if(m_NumPaths && (!m_aaStoragePaths[TYPE_SAVE][0] || !fs_makedir(m_aaStoragePaths[TYPE_SAVE])))
+		if(StorageType != STORAGETYPE_BASIC && m_NumPaths && (!m_aaStoragePaths[TYPE_SAVE][0] || !fs_makedir(m_aaStoragePaths[TYPE_SAVE])))
 		{
 			char aPath[MAX_PATH_LENGTH];
-			fs_makedir(GetPath(TYPE_SAVE, "screenshots", aPath, sizeof(aPath)));
-			fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto", aPath, sizeof(aPath)));
-			fs_makedir(GetPath(TYPE_SAVE, "maps", aPath, sizeof(aPath)));
+			if(StorageType == STORAGETYPE_CLIENT)
+			{
+				fs_makedir(GetPath(TYPE_SAVE, "screenshots", aPath, sizeof(aPath)));
+				fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto", aPath, sizeof(aPath)));
+				fs_makedir(GetPath(TYPE_SAVE, "maps", aPath, sizeof(aPath)));
+				fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath)));
+			}
 			fs_makedir(GetPath(TYPE_SAVE, "dumps", aPath, sizeof(aPath)));
-			fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath)));
 			fs_makedir(GetPath(TYPE_SAVE, "demos", aPath, sizeof(aPath)));
 			fs_makedir(GetPath(TYPE_SAVE, "demos/auto", aPath, sizeof(aPath)));
 		}
@@ -377,10 +380,22 @@ public:
 		return !fs_makedir(GetPath(Type, pFoldername, aBuffer, sizeof(aBuffer)));
 	}
 
-	static IStorage *Create(const char *pApplicationName, int NumArgs, const char **ppArguments)
+	virtual void GetCompletePath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize)
+	{
+		if(Type < 0 || Type >= m_NumPaths)
+		{
+			if(BufferSize > 0)
+				pBuffer[0] = 0;
+			return;
+		}
+
+		GetPath(Type, pDir, pBuffer, BufferSize);
+	}
+
+	static IStorage *Create(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments)
 	{
 		CStorage *p = new CStorage();
-		if(p && p->Init(pApplicationName, NumArgs, ppArguments))
+		if(p && p->Init(pApplicationName, StorageType, NumArgs, ppArguments))
 		{
 			dbg_msg("storage", "initialisation failed");
 			delete p;
@@ -390,4 +405,4 @@ public:
 	}
 };
 
-IStorage *CreateStorage(const char *pApplicationName, int NumArgs, const char **ppArguments) { return CStorage::Create(pApplicationName, NumArgs, ppArguments); }
+IStorage *CreateStorage(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) { return CStorage::Create(pApplicationName, StorageType, NumArgs, ppArguments); }