about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/base/system.c43
-rw-r--r--src/base/system.h53
-rw-r--r--src/base/tl/threading.h106
-rw-r--r--src/engine/client.h4
-rw-r--r--src/engine/client/backend_sdl.cpp495
-rw-r--r--src/engine/client/backend_sdl.h199
-rw-r--r--src/engine/client/client.cpp136
-rw-r--r--src/engine/client/client.h15
-rw-r--r--src/engine/client/graphics.cpp55
-rw-r--r--src/engine/client/graphics.h9
-rw-r--r--src/engine/client/graphics_threaded.cpp892
-rw-r--r--src/engine/client/graphics_threaded.h443
-rw-r--r--src/engine/client/serverbrowser.cpp12
-rw-r--r--src/engine/client/sound.cpp7
-rw-r--r--src/engine/client/text.cpp58
-rw-r--r--src/engine/console.h1
-rw-r--r--src/engine/external/pnglite/VERSION1
-rw-r--r--src/engine/external/wavpack/VERSION1
-rw-r--r--src/engine/graphics.h14
-rw-r--r--src/engine/server.h6
-rw-r--r--src/engine/server/server.cpp426
-rw-r--r--src/engine/server/server.h35
-rw-r--r--src/engine/shared/config.cpp7
-rw-r--r--src/engine/shared/config.h3
-rw-r--r--src/engine/shared/config_variables.h15
-rw-r--r--src/engine/shared/console.cpp25
-rw-r--r--src/engine/shared/console.h3
-rw-r--r--src/engine/shared/econ.cpp36
-rw-r--r--src/engine/shared/econ.h5
-rw-r--r--src/engine/shared/masterserver.cpp6
-rw-r--r--src/engine/shared/netban.cpp615
-rw-r--r--src/engine/shared/netban.h184
-rw-r--r--src/engine/shared/network.h73
-rw-r--r--src/engine/shared/network_console.cpp97
-rw-r--r--src/engine/shared/network_server.cpp256
-rw-r--r--src/game/client/components/console.cpp8
-rw-r--r--src/game/client/components/damageind.cpp17
-rw-r--r--src/game/client/components/damageind.h2
-rw-r--r--src/game/client/components/hud.cpp2
-rw-r--r--src/game/client/components/items.cpp3
-rw-r--r--src/game/client/components/maplayers.cpp7
-rw-r--r--src/game/client/components/menus.cpp2
-rw-r--r--src/game/client/gameclient.cpp14
-rw-r--r--src/game/server/gamecontext.cpp95
-rw-r--r--src/game/server/gamecontext.h5
-rw-r--r--src/game/server/gamecontroller.cpp4
-rw-r--r--src/game/server/gamemodes/ctf.cpp2
-rw-r--r--src/game/server/player.cpp9
-rw-r--r--src/game/server/player.h2
-rw-r--r--src/game/variables.h1
-rw-r--r--src/mastersrv/mastersrv.cpp88
-rw-r--r--src/tools/crapnet.cpp4
-rw-r--r--src/tools/fake_server.cpp2
-rw-r--r--src/tools/tileset_borderadd.cpp86
-rw-r--r--src/tools/tileset_borderrem.cpp86
-rw-r--r--src/tools/tileset_borderset.cpp78
56 files changed, 3940 insertions, 913 deletions
diff --git a/src/base/system.c b/src/base/system.c
index 4ea6d413..a11caf4c 100644
--- a/src/base/system.c
+++ b/src/base/system.c
@@ -125,7 +125,7 @@ static IOHANDLE logfile = 0;
 static void logger_file(const char *line)
 {
 	io_write(logfile, line, strlen(line));
-	io_write(logfile, "\n", 1);
+	io_write_newline(logfile);
 	io_flush(logfile);
 }
 
@@ -142,8 +142,6 @@ void dbg_logger_file(const char *filename)
 }
 /* */
 
-int memory_alloced = 0;
-
 typedef struct MEMHEADER
 {
 	const char *filename;
@@ -165,8 +163,10 @@ void *mem_alloc_debug(const char *filename, int line, unsigned size, unsigned al
 {
 	/* TODO: fix alignment */
 	/* TODO: add debugging */
+	MEMTAIL *tail;
 	MEMHEADER *header = (struct MEMHEADER *)malloc(size+sizeof(MEMHEADER)+sizeof(MEMTAIL));
-	MEMTAIL *tail = (struct MEMTAIL *)(((char*)(header+1))+size);
+	dbg_assert(header != 0, "mem_alloc failure");
+	tail = (struct MEMTAIL *)(((char*)(header+1))+size);
 	header->size = size;
 	header->filename = filename;
 	header->line = line;
@@ -222,8 +222,9 @@ void mem_debug_dump(IOHANDLE file)
 	{
 		while(header)
 		{
-			str_format(buf, sizeof(buf), "%s(%d): %d\n", header->filename, header->line, header->size);
+			str_format(buf, sizeof(buf), "%s(%d): %d", header->filename, header->line, header->size);
 			io_write(file, buf, strlen(buf));
+			io_write_newline(file);
 			header = header->next;
 		}
 
@@ -346,6 +347,15 @@ unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
 	return fwrite(buffer, 1, size, (FILE*)io);
 }
 
+unsigned io_write_newline(IOHANDLE io)
+{
+#if defined(CONF_FAMILY_WINDOWS)
+	return fwrite("\r\n", 1, 2, (FILE*)io);
+#else
+	return fwrite("\n", 1, 1, (FILE*)io);
+#endif
+}
+
 int io_close(IOHANDLE io)
 {
 	fclose((FILE*)io);
@@ -495,6 +505,21 @@ void lock_release(LOCK lock)
 #endif
 }
 
+#if defined(CONF_FAMILY_UNIX)
+void semaphore_init(SEMAPHORE *sem) { sem_init(sem, 0, 0); }
+void semaphore_wait(SEMAPHORE *sem) { sem_wait(sem); }
+void semaphore_signal(SEMAPHORE *sem) { sem_post(sem); }
+void semaphore_destroy(SEMAPHORE *sem) { sem_destroy(sem); }
+#elif defined(CONF_FAMILY_WINDOWS)
+void semaphore_init(SEMAPHORE *sem) { (HANDLE)(*sem) = CreateSemaphore(0, 0, 10000, 0); }
+void semaphore_wait(SEMAPHORE *sem) { WaitForSingleObject((HANDLE)*sem, 0L); }
+void semaphore_signal(SEMAPHORE *sem) { ReleaseSemaphore((HANDLE)*sem, 1, NULL); }
+void semaphore_destroy(SEMAPHORE *sem) { CloseHandle((HANDLE)*sem); }
+#else
+	#error not implemented on this platform
+#endif
+
+
 /* -----  time ----- */
 int64 time_get()
 {
@@ -585,18 +610,18 @@ int net_addr_comp(const NETADDR *a, const NETADDR *b)
 	return mem_comp(a, b, sizeof(NETADDR));
 }
 
-void net_addr_str(const NETADDR *addr, char *string, int max_length)
+void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port)
 {
 	if(addr->type == NETTYPE_IPV4)
 	{
-		if(addr->port != 0)
+		if(add_port != 0)
 			str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port);
 		else
 			str_format(string, max_length, "%d.%d.%d.%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]);
 	}
 	else if(addr->type == NETTYPE_IPV6)
 	{
-		if(addr->port != 0)
+		if(add_port != 0)
 			str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
 				(addr->ip[0]<<8)|addr->ip[1], (addr->ip[2]<<8)|addr->ip[3], (addr->ip[4]<<8)|addr->ip[5], (addr->ip[6]<<8)|addr->ip[7],
 				(addr->ip[8]<<8)|addr->ip[9], (addr->ip[10]<<8)|addr->ip[11], (addr->ip[12]<<8)|addr->ip[13], (addr->ip[14]<<8)|addr->ip[15],
@@ -1500,7 +1525,7 @@ int net_socket_read_wait(NETSOCKET sock, int time)
 	return 0;
 }
 
-unsigned time_timestamp()
+int time_timestamp()
 {
 	return time(0);
 }
diff --git a/src/base/system.h b/src/base/system.h
index aaa5b43f..b3588dbf 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -240,6 +240,18 @@ unsigned io_skip(IOHANDLE io, int size);
 unsigned io_write(IOHANDLE io, const void *buffer, unsigned size);
 
 /*
+	Function: io_write_newline
+		Writes newline to file.
+
+	Parameters:
+		io - Handle to the file.
+
+	Returns:
+		Number of bytes written.
+*/
+unsigned io_write_newline(IOHANDLE io);
+
+/*
 	Function: io_seek
 		Seeks to a specified offset in the file.
 
@@ -388,6 +400,23 @@ int lock_try(LOCK lock);
 void lock_wait(LOCK lock);
 void lock_release(LOCK lock);
 
+
+/* Group: Semaphores */
+
+#if defined(CONF_FAMILY_UNIX)
+	#include <semaphore.h>
+	typedef sem_t SEMAPHORE;
+#elif defined(CONF_FAMILY_WINDOWS)
+	typedef void* SEMAPHORE;
+#else
+	#error missing sempahore implementation
+#endif
+
+void semaphore_init(SEMAPHORE *sem);
+void semaphore_wait(SEMAPHORE *sem);
+void semaphore_signal(SEMAPHORE *sem);
+void semaphore_destroy(SEMAPHORE *sem);
+
 /* Group: Timer */
 #ifdef __GNUC__
 /* if compiled with -pedantic-errors it will complain about long
@@ -425,7 +454,7 @@ int64 time_freq();
 	Returns:
 		The time as a UNIX timestamp
 */
-unsigned time_timestamp();
+int time_timestamp();
 
 /* Group: Network General */
 typedef struct
@@ -499,12 +528,13 @@ int net_addr_comp(const NETADDR *a, const NETADDR *b);
 		addr - Address to turn into a string.
 		string - Buffer to fill with the string.
 		max_length - Maximum size of the string.
+		add_port - add port to string or not
 
 	Remarks:
 		- The string will always be zero terminated
 
 */
-void net_addr_str(const NETADDR *addr, char *string, int max_length);
+void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port);
 
 /*
 	Function: net_addr_from_str
@@ -1130,25 +1160,6 @@ void mem_debug_dump(IOHANDLE file);
 
 void swap_endian(void *data, unsigned elem_size, unsigned num);
 
-/* Group: Debug levels */
-//by format
-enum {
-	DBG_FMT_RAW				= 1,	//raw output
-	DBG_FMT_TIME			= 2,	//show time
-	DBG_FMT_SYS				= 3,	//show sys
-	DBG_FMT_FULL			= 4		//show both
-};
-
-enum {
-	DBG_LEVEL_IMPORTANT			= 0,	//important always showed messages
-	DBG_LEVEL_ERROR				= 1,	//error messages
-	DBG_LEVEL_WARNING			= 2,	//warning messages
-	DBG_LEVEL_MSG				= 3,	//extra debug messages
-	DBG_LEVEL_INFO				= 4		//info messages
-};
-
-#define DBG_LEVEL_LOW DBG_LEVEL_IMPORTANT
-#define DBG_LEVEL_HIGH DBG_LEVEL_INFO
 
 typedef void (*DBG_LOGGER)(const char *line);
 void dbg_logger(DBG_LOGGER logger);
diff --git a/src/base/tl/threading.h b/src/base/tl/threading.h
new file mode 100644
index 00000000..dbf788cd
--- /dev/null
+++ b/src/base/tl/threading.h
@@ -0,0 +1,106 @@
+
+#pragma once
+
+#include "../system.h"
+
+/*
+	atomic_inc - should return the value after increment
+	atomic_dec - should return the value after decrement
+	atomic_compswap - should return the value before the eventual swap
+	
+*/
+
+#if defined(__GNUC__)
+
+	inline unsigned atomic_inc(volatile unsigned *pValue)
+	{
+		return __sync_fetch_and_add(pValue, 1);
+	}
+
+	inline unsigned atomic_dec(volatile unsigned *pValue)
+	{
+		return __sync_fetch_and_add(pValue, -1);
+	}
+
+	inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value)
+	{
+		return __sync_val_compare_and_swap(pValue, comperand, value);
+	}
+
+	inline void sync_barrier()
+	{
+		__sync_synchronize();
+	}
+
+#elif defined(_MSC_VER)
+	#include <intrin.h>
+
+	inline unsigned atomic_inc(volatile unsigned *pValue)
+	{
+		return _InterlockedIncrement((volatile long *)pValue);
+	}
+	
+	inline unsigned atomic_dec(volatile unsigned *pValue)
+	{
+		return _InterlockedDecrement((volatile long *)pValue);
+	}
+
+	inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value)
+	{
+		return _InterlockedCompareExchange((volatile long *)pValue, (long)value, (long)comperand);
+	}
+
+	inline void sync_barrier()
+	{
+		_ReadWriteBarrier();
+	}
+#else
+	#error missing atomic implementation for this compiler
+#endif
+
+class semaphore
+{
+	SEMAPHORE sem;
+public:
+	semaphore() { semaphore_init(&sem); }
+	~semaphore() { semaphore_destroy(&sem); }
+	void wait() { semaphore_wait(&sem); }
+	void signal() { semaphore_signal(&sem); }
+};
+
+class lock
+{
+	friend class scope_lock;
+
+	LOCK var;
+
+	void take() { lock_wait(var); }
+	void release() { lock_release(var); }
+
+public:
+	lock()
+	{
+		var = lock_create();
+	}
+
+	~lock()
+	{
+		lock_destroy(var);
+	}
+};
+
+class scope_lock
+{
+	lock *var;
+public:
+	scope_lock(lock *l)
+	{
+		var = l;
+		var->take();
+	}
+
+	~scope_lock()
+	{
+		var->release();
+	}
+};
diff --git a/src/engine/client.h b/src/engine/client.h
index 966e8f61..e9076b76 100644
--- a/src/engine/client.h
+++ b/src/engine/client.h
@@ -23,7 +23,7 @@ protected:
 	float m_PredIntraTick;
 
 	float m_LocalTime;
-	float m_FrameTime;
+	float m_RenderFrameTime;
 
 	int m_GameTickSpeed;
 public:
@@ -68,7 +68,7 @@ public:
 	inline int GameTickSpeed() const { return m_GameTickSpeed; }
 
 	// other time access
-	inline float FrameTime() const { return m_FrameTime; }
+	inline float RenderFrameTime() const { return m_RenderFrameTime; }
 	inline float LocalTime() const { return m_LocalTime; }
 
 	// actions
diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp
new file mode 100644
index 00000000..691d193c
--- /dev/null
+++ b/src/engine/client/backend_sdl.cpp
@@ -0,0 +1,495 @@
+
+#include "SDL.h"
+#include "SDL_opengl.h"
+
+#include "graphics_threaded.h"
+#include "backend_sdl.h"
+
+// ------------ CGraphicsBackend_Threaded
+
+void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)
+{
+	CGraphicsBackend_Threaded *pThis = (CGraphicsBackend_Threaded *)pUser;
+
+	while(!pThis->m_Shutdown)
+	{
+		pThis->m_Activity.wait();
+		if(pThis->m_pBuffer)
+		{
+			pThis->m_pProcessor->RunBuffer(pThis->m_pBuffer);
+			sync_barrier();
+			pThis->m_pBuffer = 0x0;
+			pThis->m_BufferDone.signal();
+		}
+	}
+}
+
+CGraphicsBackend_Threaded::CGraphicsBackend_Threaded()
+{
+	m_pBuffer = 0x0;
+	m_pProcessor = 0x0;
+	m_pThread = 0x0;
+}
+
+void CGraphicsBackend_Threaded::StartProcessor(ICommandProcessor *pProcessor)
+{
+	m_Shutdown = false;
+	m_pProcessor = pProcessor;
+	m_pThread = thread_create(ThreadFunc, this);
+	m_BufferDone.signal();
+}
+
+void CGraphicsBackend_Threaded::StopProcessor()
+{
+	m_Shutdown = true;
+	m_Activity.signal();
+	thread_wait(m_pThread);
+	thread_destroy(m_pThread);
+}
+
+void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
+{
+	WaitForIdle();
+	m_pBuffer = pBuffer;
+	m_Activity.signal();
+}
+
+bool CGraphicsBackend_Threaded::IsIdle() const
+{
+	return m_pBuffer == 0x0;
+}
+
+void CGraphicsBackend_Threaded::WaitForIdle()
+{
+	while(m_pBuffer != 0x0)
+		m_BufferDone.wait();
+}
+
+
+// ------------ CCommandProcessorFragment_General
+
+void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand)
+{
+	pCommand->m_pSemaphore->signal();
+}
+
+bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SCommand * pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_NOP: break;
+	case CCommandBuffer::CMD_SIGNAL: Cmd_Signal(static_cast<const CCommandBuffer::SCommand_Signal *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+// ------------ CCommandProcessorFragment_OpenGL
+
+int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat)
+{
+	if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) return GL_RGB;
+	if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) return GL_ALPHA;
+	if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) return GL_RGBA;
+	return GL_RGBA;
+}
+
+void CCommandProcessorFragment_OpenGL::SetState(const CCommandBuffer::SState &State)
+{
+	// blend
+	switch(State.m_BlendMode)
+	{
+	case CCommandBuffer::BLEND_NONE:
+		glDisable(GL_BLEND);
+		break;
+	case CCommandBuffer::BLEND_ALPHA:
+		glEnable(GL_BLEND);
+		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+		break;
+	case CCommandBuffer::BLEND_ADDITIVE:
+		glEnable(GL_BLEND);
+		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+		break;
+	default:
+		dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode);
+	};
+
+	// clip
+	if(State.m_ClipEnable)
+	{
+		glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH);
+		glEnable(GL_SCISSOR_TEST);
+	}
+	else
+		glDisable(GL_SCISSOR_TEST);
+	
+	// texture
+	if(State.m_Texture >= 0 && State.m_Texture < CCommandBuffer::MAX_TEXTURES)
+	{
+		glEnable(GL_TEXTURE_2D);
+		glBindTexture(GL_TEXTURE_2D, m_aTextures[State.m_Texture]);
+	}
+	else
+		glDisable(GL_TEXTURE_2D);
+
+	// screen mapping
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(State.m_ScreenTL.x, State.m_ScreenBR.x, State.m_ScreenBR.y, State.m_ScreenTL.y, 1.0f, 10.f);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand)
+{
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot]);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height,
+		TexFormatToOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pCommand->m_pData);
+	mem_free(pCommand->m_pData);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand)
+{
+	glDeleteTextures(1, &m_aTextures[pCommand->m_Slot]);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand)
+{
+	int Oglformat = TexFormatToOpenGLFormat(pCommand->m_Format);
+	int StoreOglformat = TexFormatToOpenGLFormat(pCommand->m_StoreFormat);
+
+	glGenTextures(1, &m_aTextures[pCommand->m_Slot]);
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot]);
+
+	if(pCommand->m_Flags&CCommandBuffer::TEXFLAG_NOMIPMAPS)
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, pCommand->m_Width, pCommand->m_Height, 0, Oglformat, GL_UNSIGNED_BYTE, pCommand->m_pData);
+	}
+	else
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+		gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, pCommand->m_Width, pCommand->m_Height, Oglformat, GL_UNSIGNED_BYTE, pCommand->m_pData);
+	}
+
+	mem_free(pCommand->m_pData);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand)
+{
+	glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand)
+{
+	SetState(pCommand->m_State);
+	
+	glVertexPointer(3, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices);
+	glTexCoordPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*3);
+	glColorPointer(4, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*5);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glEnableClientState(GL_COLOR_ARRAY);
+
+	switch(pCommand->m_PrimType)
+	{
+	case CCommandBuffer::PRIMTYPE_QUADS:
+		glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount*4);
+		break;
+	case CCommandBuffer::PRIMTYPE_LINES:
+		glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount*2);
+		break;
+	default:
+		dbg_msg("render", "unknown primtype %d\n", pCommand->m_Cmd);
+	};
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand)
+{
+	// fetch image data
+	GLint aViewport[4] = {0,0,0,0};
+	glGetIntegerv(GL_VIEWPORT, aViewport);
+
+	int w = aViewport[2];
+	int h = aViewport[3];
+
+	// we allocate one more row to use when we are flipping the texture
+	unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*3, 1);
+	unsigned char *pTempRow = pPixelData+w*h*3;
+
+	// fetch the pixels
+	GLint Alignment;
+	glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment);
+	glPixelStorei(GL_PACK_ALIGNMENT, 1);
+	glReadPixels(0,0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData);
+	glPixelStorei(GL_PACK_ALIGNMENT, Alignment);
+
+	// flip the pixel because opengl works from bottom left corner
+	for(int y = 0; y < h/2; y++)
+	{
+		mem_copy(pTempRow, pPixelData+y*w*3, w*3);
+		mem_copy(pPixelData+y*w*3, pPixelData+(h-y-1)*w*3, w*3);
+		mem_copy(pPixelData+(h-y-1)*w*3, pTempRow,w*3);
+	}
+
+	// fill in the information
+	pCommand->m_pImage->m_Width = w;
+	pCommand->m_pImage->m_Height = h;
+	pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGB;
+	pCommand->m_pImage->m_pData = pPixelData;
+}
+
+CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL()
+{
+	mem_zero(m_aTextures, sizeof(m_aTextures));
+}
+
+bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand * pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_TEXTURE_CREATE: Cmd_Texture_Create(static_cast<const CCommandBuffer::SCommand_Texture_Create *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_TEXTURE_DESTROY: Cmd_Texture_Destroy(static_cast<const CCommandBuffer::SCommand_Texture_Destroy *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_TEXTURE_UPDATE: Cmd_Texture_Update(static_cast<const CCommandBuffer::SCommand_Texture_Update *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_CLEAR: Cmd_Clear(static_cast<const CCommandBuffer::SCommand_Clear *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_RENDER: Cmd_Render(static_cast<const CCommandBuffer::SCommand_Render *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_SCREENSHOT: Cmd_Screenshot(static_cast<const CCommandBuffer::SCommand_Screenshot *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+
+// ------------ CCommandProcessorFragment_SDL
+
+void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand)
+{
+	m_GLContext = pCommand->m_Context;
+	GL_MakeCurrent(m_GLContext);
+
+	// set some default settings
+	glEnable(GL_BLEND);
+	glDisable(GL_CULL_FACE);
+	glDisable(GL_DEPTH_TEST);
+	glMatrixMode(GL_MODELVIEW);
+	glLoadIdentity();
+
+	glAlphaFunc(GL_GREATER, 0);
+	glEnable(GL_ALPHA_TEST);
+	glDepthMask(0);
+}
+
+void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand)
+{
+	GL_ReleaseContext(m_GLContext);
+}
+
+void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand)
+{
+	GL_SwapBuffers(m_GLContext);
+}
+
+void CCommandProcessorFragment_SDL::Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand)
+{
+	// TODO: fix this code on osx or windows
+	SDL_Rect **ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN);
+	if(ppModes == NULL)
+	{
+		// no modes
+		*pCommand->m_pNumModes = 0;
+	}
+	else if(ppModes == (SDL_Rect**)-1)
+	{
+		// no modes
+		*pCommand->m_pNumModes = 0;
+	}
+	else
+	{
+		int NumModes = 0;
+		for(int i = 0; ppModes[i]; ++i)
+		{
+			if(NumModes == pCommand->m_MaxModes)
+				break;
+			pCommand->m_pModes[NumModes].m_Width = ppModes[i]->w;
+			pCommand->m_pModes[NumModes].m_Height = ppModes[i]->h;
+			pCommand->m_pModes[NumModes].m_Red = 8;
+			pCommand->m_pModes[NumModes].m_Green = 8;
+			pCommand->m_pModes[NumModes].m_Blue = 8;
+			NumModes++;
+		}
+
+		*pCommand->m_pNumModes = NumModes;
+	}
+}
+
+CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL()
+{
+}
+
+bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast<const CCommandBuffer::SCommand_Swap *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast<const CCommandBuffer::SCommand_VideoModes *>(pBaseCommand)); break;
+	case CMD_INIT: Cmd_Init(static_cast<const SCommand_Init *>(pBaseCommand)); break;
+	case CMD_SHUTDOWN: Cmd_Shutdown(static_cast<const SCommand_Shutdown *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+// ------------ CCommandProcessor_SDL_OpenGL
+
+void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer)
+{
+	unsigned CmdIndex = 0;
+	while(1)
+	{
+		const CCommandBuffer::SCommand *pBaseCommand = pBuffer->GetCommand(&CmdIndex);
+		if(pBaseCommand == 0x0)
+			break;
+		
+		if(m_OpenGL.RunCommand(pBaseCommand))
+			continue;
+		
+		if(m_SDL.RunCommand(pBaseCommand))
+			continue;
+
+		if(m_General.RunCommand(pBaseCommand))
+			continue;
+		
+		dbg_msg("graphics", "unknown command %d", pBaseCommand->m_Cmd);
+	}
+}
+
+// ------------ CGraphicsBackend_SDL_OpenGL
+
+int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags)
+{
+	if(!SDL_WasInit(SDL_INIT_VIDEO))
+	{
+		if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
+		{
+			dbg_msg("gfx", "unable to init SDL video: %s", SDL_GetError());
+			return -1;
+		}
+
+		#ifdef CONF_FAMILY_WINDOWS
+			if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) // ignore_convention
+				putenv("SDL_VIDEO_WINDOW_POS=8,27"); // ignore_convention
+		#endif
+	}
+
+	const SDL_VideoInfo *pInfo = SDL_GetVideoInfo();
+
+	// set flags
+	int SdlFlags = SDL_OPENGL;
+	if(Flags&IGraphicsBackend::INITFLAG_RESIZABLE)
+		SdlFlags |= SDL_RESIZABLE;
+
+	if(pInfo->hw_available) // ignore_convention
+		SdlFlags |= SDL_HWSURFACE;
+	else
+		SdlFlags |= SDL_SWSURFACE;
+
+	if(pInfo->blit_hw) // ignore_convention
+		SdlFlags |= SDL_HWACCEL;
+
+	if(Flags&IGraphicsBackend::INITFLAG_FULLSCREEN)
+		SdlFlags |= SDL_FULLSCREEN;
+
+	// set gl attributes
+	if(FsaaSamples)
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, FsaaSamples);
+	}
+	else
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+	}
+
+	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, Flags&CCommandBuffer::INITFLAG_VSYNC ? 1 : 0);
+
+	// set caption
+	SDL_WM_SetCaption(pName, pName);
+
+	// create window
+	m_pScreenSurface = SDL_SetVideoMode(Width, Height, 0, SdlFlags);
+	if(!m_pScreenSurface)
+	{
+		dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError());
+		//*pCommand->m_pResult = -1;
+		return -1;
+	}		
+
+	SDL_ShowCursor(0);
+
+	// fetch gl contexts and release the context from this thread
+	m_GLContext = GL_GetCurrentContext();
+	GL_ReleaseContext(m_GLContext);
+
+	// start the command processor
+	m_pProcessor = new CCommandProcessor_SDL_OpenGL;
+	StartProcessor(m_pProcessor);
+
+	// issue a init command
+	CCommandBuffer CmdBuffer(1024, 512);
+	CCommandProcessorFragment_SDL::SCommand_Init Cmd;
+	Cmd.m_Context = m_GLContext;
+	CmdBuffer.AddCommand(Cmd);
+	RunBuffer(&CmdBuffer);
+	WaitForIdle();
+
+	// return
+	return 0;
+}
+
+int CGraphicsBackend_SDL_OpenGL::Shutdown()
+{
+	// issue a shutdown command
+	CCommandBuffer CmdBuffer(1024, 512);
+	CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd;
+	CmdBuffer.AddCommand(Cmd);
+	RunBuffer(&CmdBuffer);
+	WaitForIdle();
+			
+	// stop and delete the processor
+	StopProcessor();
+	delete m_pProcessor;
+	m_pProcessor = 0;
+
+	SDL_QuitSubSystem(SDL_INIT_VIDEO);
+	return 0;
+}
+
+void CGraphicsBackend_SDL_OpenGL::Minimize()
+{
+	SDL_WM_IconifyWindow();
+}
+
+void CGraphicsBackend_SDL_OpenGL::Maximize()
+{
+	// TODO: SDL
+}
+
+int CGraphicsBackend_SDL_OpenGL::WindowActive()
+{
+	return SDL_GetAppState()&SDL_APPINPUTFOCUS;
+}
+
+int CGraphicsBackend_SDL_OpenGL::WindowOpen()
+{
+	return SDL_GetAppState()&SDL_APPACTIVE;
+
+}
+
+
+IGraphicsBackend *CreateGraphicsBackend() { return new CGraphicsBackend_SDL_OpenGL; }
diff --git a/src/engine/client/backend_sdl.h b/src/engine/client/backend_sdl.h
new file mode 100644
index 00000000..62ac6f7c
--- /dev/null
+++ b/src/engine/client/backend_sdl.h
@@ -0,0 +1,199 @@
+
+#include "SDL.h"
+#include "SDL_opengl.h"
+
+#include "graphics_threaded.h"
+
+
+
+// platform dependent implementations for transfering render context from the main thread to the graphics thread
+// TODO: when SDL 1.3 comes, this can be removed
+#if defined(CONF_FAMILY_WINDOWS)
+	struct SGLContext
+	{
+		HDC m_hDC;
+		HGLRC m_hGLRC;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_hDC = wglGetCurrentDC();
+		Context.m_hGLRC = wglGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { wglMakeCurrent(Context.m_hDC, Context.m_hGLRC); }
+	static void GL_ReleaseContext(const SGLContext &Context) { wglMakeCurrent(NULL, NULL); }
+	static void GL_SwapBuffers(const SGLContext &Context) { SwapBuffers(Context.m_hDC); }
+#elif defined(CONF_PLATFORM_MACOSX)
+	#warning Untested implementation. I have no Mac OS X machine to test on. Please test, verify, fix and then remove this warning
+
+	struct SGLContext
+	{
+		AGLDrawable m_Drawable;
+		AGLContext m_Context;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_Drawable = aglGetCurrentDrawable();
+		Context.m_Context = aglGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { aglMakeCurrent(Context.m_Drawable, Context.m_Context); }
+	static void GL_ReleaseContext(const SGLContext &Context) { aglMakeCurrent(AGL_NONE, NULL); }
+	static void GL_SwapBuffers(const SGLContext &Context) { aglSwapBuffers(Context.m_Drawable); }
+		
+#elif defined(CONF_FAMILY_UNIX)
+
+	#include <GL/glx.h>
+
+	struct SGLContext
+	{
+		Display *m_pDisplay;
+		GLXDrawable m_Drawable;
+		GLXContext m_Context;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_pDisplay = glXGetCurrentDisplay();
+		Context.m_Drawable = glXGetCurrentDrawable();
+		Context.m_Context = glXGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, Context.m_Drawable, Context.m_Context); }
+	static void GL_ReleaseContext(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, None, 0x0); }
+	static void GL_SwapBuffers(const SGLContext &Context) { glXSwapBuffers(Context.m_pDisplay, Context.m_Drawable); }
+#else
+	#error missing implementation
+#endif
+
+
+// basic threaded backend, abstract, missing init and shutdown functions
+class CGraphicsBackend_Threaded : public IGraphicsBackend
+{
+public:
+	// constructed on the main thread, the rest of the functions is runned on the render thread
+	class ICommandProcessor
+	{
+	public:
+		virtual ~ICommandProcessor() {}
+		virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
+	};
+
+	CGraphicsBackend_Threaded();
+
+	virtual void RunBuffer(CCommandBuffer *pBuffer);
+	virtual bool IsIdle() const;
+	virtual void WaitForIdle();
+		
+protected:
+	void StartProcessor(ICommandProcessor *pProcessor);
+	void StopProcessor();
+
+private:
+	ICommandProcessor *m_pProcessor;
+	CCommandBuffer * volatile m_pBuffer;
+	volatile bool m_Shutdown;
+	semaphore m_Activity;
+	semaphore m_BufferDone;
+	void *m_pThread;
+
+	static void ThreadFunc(void *pUser);
+};
+
+// takes care of implementation independent operations
+class CCommandProcessorFragment_General
+{
+	void Cmd_Nop();
+	void Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand);
+public:
+	bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand);
+};
+
+// takes care of opengl related rendering
+class CCommandProcessorFragment_OpenGL
+{
+	GLuint m_aTextures[CCommandBuffer::MAX_TEXTURES];
+	static int TexFormatToOpenGLFormat(int TexFormat);
+
+	void SetState(const CCommandBuffer::SState &State);
+
+	void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand);
+	void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand);
+	void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand);
+	void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand);
+	void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand);
+	void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand);
+
+public:
+	CCommandProcessorFragment_OpenGL();
+
+	bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand);
+};
+
+// takes care of sdl related commands
+class CCommandProcessorFragment_SDL
+{
+	// SDL stuff
+	SGLContext m_GLContext;
+public:
+	enum
+	{
+		CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM,
+		CMD_SHUTDOWN,
+	};
+
+	struct SCommand_Init : public CCommandBuffer::SCommand
+	{
+		SCommand_Init() : SCommand(CMD_INIT) {}
+		SGLContext m_Context;
+	};
+
+	struct SCommand_Shutdown : public CCommandBuffer::SCommand
+	{
+		SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {}
+	};
+
+private:
+	void Cmd_Init(const SCommand_Init *pCommand);
+	void Cmd_Shutdown(const SCommand_Shutdown *pCommand);
+	void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand);
+	void Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand);
+public:
+	CCommandProcessorFragment_SDL();
+
+	bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand);
+};
+
+// command processor impelementation, uses the fragments to combine into one processor
+class CCommandProcessor_SDL_OpenGL : public CGraphicsBackend_Threaded::ICommandProcessor
+{
+ 	CCommandProcessorFragment_OpenGL m_OpenGL;
+ 	CCommandProcessorFragment_SDL m_SDL;
+ 	CCommandProcessorFragment_General m_General;
+ public:
+	virtual void RunBuffer(CCommandBuffer *pBuffer);
+};
+
+// graphics backend implemented with SDL and OpenGL
+class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded
+{
+	SDL_Surface *m_pScreenSurface;
+	ICommandProcessor *m_pProcessor;
+	SGLContext m_GLContext;
+public:
+	virtual int Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags);
+	virtual int Shutdown();
+
+	virtual void Minimize();
+	virtual void Maximize();
+	virtual int WindowActive();
+	virtual int WindowOpen();
+};
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 9f43b9ce..5d757c49 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -7,6 +7,7 @@
 
 #include <base/math.h>
 #include <base/system.h>
+#include <base/tl/threading.h>
 
 #include <engine/client.h>
 #include <engine/config.h>
@@ -48,6 +49,10 @@
 	#include <windows.h>
 #endif
 
+#include "SDL.h"
+#ifdef main
+#undef main
+#endif
 
 void CGraph::Init(float Min, float Max)
 {
@@ -243,10 +248,11 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD
 	m_pMap = 0;
 	m_pConsole = 0;
 
-	m_FrameTime = 0.0001f;
-	m_FrameTimeLow = 1.0f;
-	m_FrameTimeHigh = 0.0f;
-	m_Frames = 0;
+	m_RenderFrameTime = 0.0001f;
+	m_RenderFrameTimeLow = 1.0f;
+	m_RenderFrameTimeHigh = 0.0f;
+	m_RenderFrames = 0;
+	m_LastRenderTime = time_get();
 
 	m_GameTickSpeed = SERVER_TICK_SPEED;
 
@@ -546,6 +552,7 @@ void CClient::DisconnectWithReason(const char *pReason)
 
 	//
 	m_RconAuthed = 0;
+	m_UseTempRconCommands = 0;
 	m_pConsole->DeregisterTempAll();
 	m_NetClient.Disconnect(pReason);
 	SetState(IClient::STATE_OFFLINE);
@@ -680,13 +687,13 @@ void CClient::DebugRender()
 		udp = 8
 		total = 42
 	*/
-	FrameTimeAvg = FrameTimeAvg*0.9f + m_FrameTime*0.1f;
+	FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f;
 	str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
 		m_CurGameTick, m_PredTick,
 		mem_stats()->allocated/1024,
 		mem_stats()->total_allocations,
 		Graphics()->MemoryUsage()/1024,
-		(int)(1.0f/FrameTimeAvg));
+		(int)(1.0f/FrameTimeAvg + 0.5f));
 	Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer);
 
 
@@ -964,7 +971,7 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
 			Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients)
 			return;
 
-		net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress));
+		net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true);
 
 		for(int i = 0; i < Info.m_NumClients; i++)
 		{
@@ -1153,9 +1160,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
 			int Result = Unpacker.GetInt();
 			if(Unpacker.Error() == 0)
 				m_RconAuthed = Result;
+			int Old = m_UseTempRconCommands;
 			m_UseTempRconCommands = Unpacker.GetInt();
 			if(Unpacker.Error() != 0)
 				m_UseTempRconCommands = 0;
+			if(Old != 0 && m_UseTempRconCommands == 0)
+				m_pConsole->DeregisterTempAll();
 		}
 		else if(Msg == NETMSG_RCON_LINE)
 		{
@@ -1675,7 +1685,7 @@ void CClient::InitInterfaces()
 	// fetch interfaces
 	m_pEngine = Kernel()->RequestInterface<IEngine>();
 	m_pEditor = Kernel()->RequestInterface<IEditor>();
-	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
+	//m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
 	m_pSound = Kernel()->RequestInterface<IEngineSound>();
 	m_pGameClient = Kernel()->RequestInterface<IGameClient>();
 	m_pInput = Kernel()->RequestInterface<IEngineInput>();
@@ -1690,15 +1700,40 @@ void CClient::InitInterfaces()
 
 void CClient::Run()
 {
-	int64 ReportTime = time_get();
-	int64 ReportInterval = time_freq()*1;
-
 	m_LocalStartTime = time_get();
 	m_SnapshotParts = 0;
 
+	// init SDL
+	{
+		if(SDL_Init(0) < 0)
+		{
+			dbg_msg("client", "unable to init SDL base: %s", SDL_GetError());
+			return;
+		}
+
+		atexit(SDL_Quit); // ignore_convention
+	}
+
 	// init graphics
-	if(m_pGraphics->Init() != 0)
-		return;
+	{
+		if(g_Config.m_GfxThreaded)
+			m_pGraphics = CreateEngineGraphicsThreaded();
+		else
+			m_pGraphics = CreateEngineGraphics();
+
+		bool RegisterFail = false;
+		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IEngineGraphics*>(m_pGraphics)); // register graphics as both
+		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IGraphics*>(m_pGraphics));
+
+		if(RegisterFail || m_pGraphics->Init() != 0)
+		{
+			dbg_msg("client", "couldn't init graphics");
+			return;
+		}
+	}
+
+	// init sound, allowed to fail
+	m_SoundInitFailed = Sound()->Init() != 0;
 
 	// open socket
 	{
@@ -1722,16 +1757,15 @@ void CClient::Run()
 	MasterServer()->RefreshAddresses(m_NetClient.NetType());
 
 	// init the editor
-	m_pEditor->Init();
+	//m_pEditor->Init();
 
-	// init sound, allowed to fail
-	m_SoundInitFailed = Sound()->Init() != 0;
 
 	// load data
 	if(!LoadData())
 		return;
 
 	GameClient()->OnInit();
+
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion());
 	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
@@ -1756,9 +1790,6 @@ void CClient::Run()
 
 	while (1)
 	{
-		int64 FrameStartTime = time_get();
-		m_Frames++;
-
 		//
 		VersionUpdate();
 
@@ -1847,19 +1878,38 @@ void CClient::Run()
 				m_EditorActive = false;
 
 			Update();
-
-			if(g_Config.m_DbgStress)
+			
+			if(!g_Config.m_GfxAsyncRender || m_pGraphics->IsIdle())
 			{
-				if((m_Frames%10) == 0)
+				m_RenderFrames++;
+
+				// update frametime
+				int64 Now = time_get();
+				m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq();
+				if(m_RenderFrameTime < m_RenderFrameTimeLow)
+					m_RenderFrameTimeLow = m_RenderFrameTime;
+				if(m_RenderFrameTime > m_RenderFrameTimeHigh)
+					m_RenderFrameTimeHigh = m_RenderFrameTime;
+				m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1);
+
+				m_LastRenderTime = Now;
+
+				if(g_Config.m_DbgStress)
+				{
+					if((m_RenderFrames%10) == 0)
+					{
+						Render();
+						m_pGraphics->Swap();
+					}
+				}
+				else
 				{
 					Render();
 					m_pGraphics->Swap();
 				}
-			}
-			else
-			{
-				Render();
-				m_pGraphics->Swap();
+				
+
+
 			}
 		}
 
@@ -1881,32 +1931,25 @@ void CClient::Run()
 			g_Config.m_DbgHitch = 0;
 		}
 
+		/*
 		if(ReportTime < time_get())
 		{
 			if(0 && g_Config.m_Debug)
 			{
 				dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
 					m_Frames/(float)(ReportInterval/time_freq()),
-					1.0f/m_FrameTimeHigh,
-					1.0f/m_FrameTimeLow,
+					1.0f/m_RenderFrameTimeHigh,
+					1.0f/m_RenderFrameTimeLow,
 					m_NetClient.State());
 			}
-			m_FrameTimeLow = 1;
-			m_FrameTimeHigh = 0;
-			m_Frames = 0;
+			m_RenderFrameTimeLow = 1;
+			m_RenderFrameTimeHigh = 0;
+			m_RenderFrames = 0;
 			ReportTime += ReportInterval;
-		}
-
-		// update frametime
-		m_FrameTime = (time_get()-FrameStartTime)/(float)time_freq();
-		if(m_FrameTime < m_FrameTimeLow)
-			m_FrameTimeLow = m_FrameTime;
-		if(m_FrameTime > m_FrameTimeHigh)
-			m_FrameTimeHigh = m_FrameTime;
+		}*/
 
+		// update local time
 		m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
-
-		m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1);
 	}
 
 	GameClient()->OnShutdown();
@@ -1914,6 +1957,11 @@ void CClient::Run()
 
 	m_pGraphics->Shutdown();
 	m_pSound->Shutdown();
+
+	// shutdown SDL
+	{
+		SDL_Quit();
+	}
 }
 
 
@@ -2212,7 +2260,6 @@ int main(int argc, const char **argv) // ignore_convention
 	IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
 	IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention
 	IConfig *pConfig = CreateConfig();
-	IEngineGraphics *pEngineGraphics = CreateEngineGraphics();
 	IEngineSound *pEngineSound = CreateEngineSound();
 	IEngineInput *pEngineInput = CreateEngineInput();
 	IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
@@ -2226,9 +2273,6 @@ int main(int argc, const char **argv) // ignore_convention
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig);
 
-		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineGraphics*>(pEngineGraphics)); // register graphics as both
-		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IGraphics*>(pEngineGraphics));
-
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineSound*>(pEngineSound)); // register as both
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ISound*>(pEngineSound));
 
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index 1504a4e4..1849830c 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -84,9 +84,12 @@ class CClient : public IClient, public CDemoPlayer::IListner
 	int64 m_LocalStartTime;
 
 	int m_DebugFont;
-	float m_FrameTimeLow;
-	float m_FrameTimeHigh;
-	int m_Frames;
+	
+	int64 m_LastRenderTime;
+	float m_RenderFrameTimeLow;
+	float m_RenderFrameTimeHigh;
+	int m_RenderFrames;
+
 	NETADDR m_ServerAddress;
 	int m_WindowMustRefocus;
 	int m_SnapCrcErrors;
@@ -172,6 +175,12 @@ class CClient : public IClient, public CDemoPlayer::IListner
 		class CHostLookup m_VersionServeraddr;
 	} m_VersionInfo;
 
+	semaphore m_GfxRenderSemaphore;
+	semaphore m_GfxStateSemaphore;
+	volatile int m_GfxState;
+	static void GraphicsThreadProxy(void *pThis) { ((CClient*)pThis)->GraphicsThread(); }
+	void GraphicsThread();
+
 public:
 	IEngine *Engine() { return m_pEngine; }
 	IEngineGraphics *Graphics() { return m_pGraphics; }
diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp
index 617162ae..ad3926f2 100644
--- a/src/engine/client/graphics.cpp
+++ b/src/engine/client/graphics.cpp
@@ -270,6 +270,18 @@ int CGraphics_OpenGL::UnloadTexture(int Index)
 	return 0;
 }
 
+int CGraphics_OpenGL::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData)
+{
+	int Oglformat = GL_RGBA;
+	if(Format == CImageInfo::FORMAT_RGB)
+		Oglformat = GL_RGB;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		Oglformat = GL_ALPHA;
+
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pData);
+	return 0;
+}
 
 int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
 {
@@ -336,9 +348,19 @@ int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const vo
 
 	glGenTextures(1, &m_aTextures[Tex].m_Tex);
 	glBindTexture(GL_TEXTURE_2D, m_aTextures[Tex].m_Tex);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-	gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData);
+
+	if(Flags&TEXLOAD_NOMIPMAPS)
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pData);
+	}
+	else
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+		gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData);
+	}
 
 	// calculate memory usage
 	{
@@ -685,7 +707,7 @@ void CGraphics_OpenGL::QuadsText(float x, float y, float Size, float r, float g,
 	QuadsEnd();
 }
 
-bool CGraphics_OpenGL::Init()
+int CGraphics_OpenGL::Init()
 {
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
@@ -721,7 +743,7 @@ bool CGraphics_OpenGL::Init()
 
 	m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE);
 
-	return true;
+	return 0;
 }
 
 int CGraphics_SDL::TryInit()
@@ -819,7 +841,7 @@ CGraphics_SDL::CGraphics_SDL()
 	m_pScreenSurface = 0;
 }
 
-bool CGraphics_SDL::Init()
+int CGraphics_SDL::Init()
 {
 	{
 		int Systems = SDL_INIT_VIDEO;
@@ -833,7 +855,7 @@ bool CGraphics_SDL::Init()
 		if(SDL_Init(Systems) < 0)
 		{
 			dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError());
-			return true;
+			return -1;
 		}
 	}
 
@@ -845,14 +867,14 @@ bool CGraphics_SDL::Init()
 	#endif
 
 	if(InitWindow() != 0)
-		return true;
+		return -1;
 
 	SDL_ShowCursor(0);
 
 	CGraphics_OpenGL::Init();
 
 	MapScreen(0,0,g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight);
-	return false;
+	return 0;
 }
 
 void CGraphics_SDL::Shutdown()
@@ -950,4 +972,19 @@ int CGraphics_SDL::GetVideoModes(CVideoMode *pModes, int MaxModes)
 	return NumModes;
 }
 
+// syncronization
+void CGraphics_SDL::InsertSignal(semaphore *pSemaphore)
+{
+	pSemaphore->signal();
+}
+
+bool CGraphics_SDL::IsIdle()
+{
+	return true;
+}
+
+void CGraphics_SDL::WaitForIdle()
+{
+}
+
 extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); }
diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h
index 95e9769a..3ab550dc 100644
--- a/src/engine/client/graphics.h
+++ b/src/engine/client/graphics.h
@@ -89,6 +89,7 @@ public:
 
 	virtual int UnloadTexture(int Index);
 	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags);
+	virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData);
 
 	// simple uncompressed RGBA loaders
 	virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags);
@@ -117,7 +118,7 @@ public:
 	virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num);
 	virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText);
 
-	virtual bool Init();
+	virtual int Init();
 };
 
 class CGraphics_SDL : public CGraphics_OpenGL
@@ -129,7 +130,7 @@ class CGraphics_SDL : public CGraphics_OpenGL
 public:
 	CGraphics_SDL();
 
-	virtual bool Init();
+	virtual int Init();
 	virtual void Shutdown();
 
 	virtual void Minimize();
@@ -143,6 +144,10 @@ public:
 
 	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes);
 
+	// syncronization
+	virtual void InsertSignal(semaphore *pSemaphore);
+	virtual bool IsIdle();
+	virtual void WaitForIdle();
 };
 
 #endif
diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp
new file mode 100644
index 00000000..33670f7b
--- /dev/null
+++ b/src/engine/client/graphics_threaded.cpp
@@ -0,0 +1,892 @@
+/* (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/detect.h>
+#include <base/math.h>
+
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+#include <engine/shared/config.h>
+#include <engine/graphics.h>
+#include <engine/storage.h>
+#include <engine/keys.h>
+#include <engine/console.h>
+
+#include <math.h> // cosf, sinf
+
+#include "graphics_threaded.h"
+
+static CVideoMode g_aFakeModes[] = {
+	{320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8},
+	{720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8},
+	{1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8},
+	{1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8},
+	{1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8},
+	{1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8},
+	{1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8},
+	{1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8},
+	{1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8},
+	{2048,1536,8,8,8},
+
+	{320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5},
+	{720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5},
+	{1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5},
+	{1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5},
+	{1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5},
+	{1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5},
+	{1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5},
+	{1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5},
+	{1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5},
+	{2048,1536,5,6,5}
+};
+
+void CGraphics_Threaded::FlushVertices()
+{
+	if(m_NumVertices == 0)
+		return;
+
+	int NumVerts = m_NumVertices;
+	m_NumVertices = 0;
+
+	CCommandBuffer::SCommand_Render Cmd;
+	Cmd.m_State = m_State;
+
+	if(m_Drawing == DRAWING_QUADS)
+	{
+		Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_QUADS;
+		Cmd.m_PrimCount = NumVerts/4;
+	}
+	else if(m_Drawing == DRAWING_LINES)
+	{
+		Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_LINES;
+		Cmd.m_PrimCount = NumVerts/2;
+	}
+	else
+		return;
+
+	Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts);
+	if(Cmd.m_pVertices == 0x0)
+	{
+		// kick command buffer and try again
+		KickCommandBuffer();
+
+		Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts);
+		if(Cmd.m_pVertices == 0x0)
+		{
+			dbg_msg("graphics", "failed to allocate data for vertices");
+			return;
+		}
+	}
+
+	mem_copy(Cmd.m_pVertices, m_aVertices, sizeof(CCommandBuffer::SVertex)*NumVerts);
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+void CGraphics_Threaded::AddVertices(int Count)
+{
+	m_NumVertices += Count;
+	if((m_NumVertices + Count) >= MAX_VERTICES)
+		FlushVertices();
+}
+
+void CGraphics_Threaded::Rotate4(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints)
+{
+	float c = cosf(m_Rotation);
+	float s = sinf(m_Rotation);
+	float x, y;
+	int i;
+
+	for(i = 0; i < 4; i++)
+	{
+		x = pPoints[i].m_Pos.x - rCenter.x;
+		y = pPoints[i].m_Pos.y - rCenter.y;
+		pPoints[i].m_Pos.x = x * c - y * s + rCenter.x;
+		pPoints[i].m_Pos.y = x * s + y * c + rCenter.y;
+	}
+}
+
+unsigned char CGraphics_Threaded::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp)
+{
+	int Value = 0;
+	for(int x = 0; x < ScaleW; x++)
+		for(int y = 0; y < ScaleH; y++)
+			Value += pData[((v+y)*w+(u+x))*Bpp+Offset];
+	return Value/(ScaleW*ScaleH);
+}
+
+unsigned char *CGraphics_Threaded::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData)
+{
+	unsigned char *pTmpData;
+	int ScaleW = Width/NewWidth;
+	int ScaleH = Height/NewHeight;
+
+	int Bpp = 3;
+	if(Format == CImageInfo::FORMAT_RGBA)
+		Bpp = 4;
+
+	pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1);
+
+	int c = 0;
+	for(int y = 0; y < NewHeight; y++)
+		for(int x = 0; x < NewWidth; x++, c++)
+		{
+			pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp);
+			if(Bpp == 4)
+				pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp);
+		}
+
+	return pTmpData;
+}
+
+CGraphics_Threaded::CGraphics_Threaded()
+{
+	m_State.m_ScreenTL.x = 0;
+	m_State.m_ScreenTL.y = 0;
+	m_State.m_ScreenBR.x = 0;
+	m_State.m_ScreenBR.y = 0;
+	m_State.m_ClipEnable = false;
+	m_State.m_ClipX = 0;
+	m_State.m_ClipY = 0;
+	m_State.m_ClipW = 0;
+	m_State.m_ClipH = 0;
+	m_State.m_Texture = -1;
+	m_State.m_BlendMode = CCommandBuffer::BLEND_NONE;
+
+	m_CurrentCommandBuffer = 0;
+	m_pCommandBuffer = 0x0;
+	m_apCommandBuffers[0] = 0x0;
+	m_apCommandBuffers[1] = 0x0;
+
+	m_NumVertices = 0;
+
+	m_ScreenWidth = -1;
+	m_ScreenHeight = -1;
+
+	m_Rotation = 0;
+	m_Drawing = 0;
+	m_InvalidTexture = 0;
+
+	m_TextureMemoryUsage = 0;
+
+	m_RenderEnable = true;
+	m_DoScreenshot = false;
+}
+
+void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h)
+{
+	if(x < 0)
+		w += x;
+	if(y < 0)
+		h += y;
+
+	x = clamp(x, 0, ScreenWidth());
+	y = clamp(y, 0, ScreenHeight());
+	w = clamp(w, 0, ScreenWidth()-x);
+	h = clamp(h, 0, ScreenHeight()-y);
+
+	m_State.m_ClipEnable = true;
+	m_State.m_ClipX = x;
+	m_State.m_ClipY = ScreenHeight()-(y+h);
+	m_State.m_ClipW = w;
+	m_State.m_ClipH = h;
+}
+
+void CGraphics_Threaded::ClipDisable()
+{
+	m_State.m_ClipEnable = false;
+}
+
+void CGraphics_Threaded::BlendNone()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_NONE;
+}
+
+void CGraphics_Threaded::BlendNormal()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_ALPHA;
+}
+
+void CGraphics_Threaded::BlendAdditive()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_ADDITIVE;
+}
+
+int CGraphics_Threaded::MemoryUsage() const
+{
+	return m_TextureMemoryUsage;
+}
+
+void CGraphics_Threaded::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY)
+{
+	m_State.m_ScreenTL.x = TopLeftX;
+	m_State.m_ScreenTL.y = TopLeftY;
+	m_State.m_ScreenBR.x = BottomRightX;
+	m_State.m_ScreenBR.y = BottomRightY;
+}
+
+void CGraphics_Threaded::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY)
+{
+	*pTopLeftX = m_State.m_ScreenTL.x;
+	*pTopLeftY = m_State.m_ScreenTL.y;
+	*pBottomRightX = m_State.m_ScreenBR.x;
+	*pBottomRightY = m_State.m_ScreenBR.y;
+}
+
+void CGraphics_Threaded::LinesBegin()
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice");
+	m_Drawing = DRAWING_LINES;
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_Threaded::LinesEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd without begin");
+	FlushVertices();
+	m_Drawing = 0;
+}
+
+void CGraphics_Threaded::LinesDraw(const CLineItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 2*i].m_Pos.x = pArray[i].m_X0;
+		m_aVertices[m_NumVertices + 2*i].m_Pos.y = pArray[i].m_Y0;
+		m_aVertices[m_NumVertices + 2*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 2*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 2*i + 1].m_Pos.x = pArray[i].m_X1;
+		m_aVertices[m_NumVertices + 2*i + 1].m_Pos.y = pArray[i].m_Y1;
+		m_aVertices[m_NumVertices + 2*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 2*i + 1].m_Color = m_aColor[1];
+	}
+
+	AddVertices(2*Num);
+}
+
+int CGraphics_Threaded::UnloadTexture(int Index)
+{
+	if(Index == m_InvalidTexture)
+		return 0;
+
+	if(Index < 0)
+		return 0;
+
+	CCommandBuffer::SCommand_Texture_Destroy Cmd;
+	Cmd.m_Slot = Index;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	m_aTextures[Index].m_Next = m_FirstFreeTexture;
+	m_TextureMemoryUsage -= m_aTextures[Index].m_MemSize;
+	m_FirstFreeTexture = Index;
+	return 0;
+}
+
+static int ImageFormatToTexFormat(int Format)
+{
+	if(Format == CImageInfo::FORMAT_RGB) return CCommandBuffer::TEXFORMAT_RGB;
+	if(Format == CImageInfo::FORMAT_RGBA) return CCommandBuffer::TEXFORMAT_RGBA;
+	if(Format == CImageInfo::FORMAT_ALPHA) return CCommandBuffer::TEXFORMAT_ALPHA;
+	return CCommandBuffer::TEXFORMAT_RGBA;
+}
+
+
+int CGraphics_Threaded::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData)
+{
+	CCommandBuffer::SCommand_Texture_Update Cmd;
+	Cmd.m_Slot = TextureID;
+	Cmd.m_X = x;
+	Cmd.m_Y = y;
+	Cmd.m_Width = Width;
+	Cmd.m_Height = Height;
+	Cmd.m_Format = ImageFormatToTexFormat(Format);
+
+	// calculate memory usage
+	int PixelSize = 4;
+	if(Format == CImageInfo::FORMAT_RGB)
+		PixelSize = 3;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		PixelSize = 1;
+
+	int MemSize = Width*Height*PixelSize;
+
+	// copy texture data
+	void *pTmpData = mem_alloc(MemSize, sizeof(void*));
+	mem_copy(pTmpData, pData, MemSize);
+	Cmd.m_pData = pTmpData;
+
+	//
+	m_pCommandBuffer->AddCommand(Cmd);
+	return 0;
+}
+
+int CGraphics_Threaded::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
+{
+	// don't waste memory on texture if we are stress testing
+	if(g_Config.m_DbgStress)
+		return m_InvalidTexture;
+
+	// grab texture
+	int Tex = m_FirstFreeTexture;
+	m_FirstFreeTexture = m_aTextures[Tex].m_Next;
+	m_aTextures[Tex].m_Next = -1;
+
+	CCommandBuffer::SCommand_Texture_Create Cmd;
+	Cmd.m_Slot = Tex;
+	Cmd.m_Width = Width;
+	Cmd.m_Height = Height;
+	Cmd.m_Format = ImageFormatToTexFormat(Format);
+	Cmd.m_StoreFormat = ImageFormatToTexFormat(StoreFormat);
+
+	// flags
+	Cmd.m_Flags = 0;
+	if(Flags&IGraphics::TEXLOAD_NOMIPMAPS)
+		Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NOMIPMAPS;
+
+	// calculate memory usage
+	int PixelSize = 4;
+	if(Format == CImageInfo::FORMAT_RGB)
+		PixelSize = 3;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		PixelSize = 1;
+
+	int MemSize = Width*Height*PixelSize;
+
+	// copy texture data
+	void *pTmpData = mem_alloc(MemSize, sizeof(void*));
+	mem_copy(pTmpData, pData, MemSize);
+	Cmd.m_pData = pTmpData;
+
+	//
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// calculate memory usage
+	int MemUsage = MemSize;
+	while(Width > 2 && Height > 2)
+	{
+		Width>>=1;
+		Height>>=1;
+		MemUsage += Width*Height*PixelSize;
+	}
+
+	m_TextureMemoryUsage += MemUsage;
+	//mem_free(pTmpData);
+	return Tex;
+}
+
+// simple uncompressed RGBA loaders
+int CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags)
+{
+	int l = str_length(pFilename);
+	int ID;
+	CImageInfo Img;
+
+	if(l < 3)
+		return -1;
+	if(LoadPNG(&Img, pFilename, StorageType))
+	{
+		if (StoreFormat == CImageInfo::FORMAT_AUTO)
+			StoreFormat = Img.m_Format;
+
+		ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags);
+		mem_free(Img.m_pData);
+		if(ID != m_InvalidTexture && g_Config.m_Debug)
+			dbg_msg("graphics/texture", "loaded %s", pFilename);
+		return ID;
+	}
+
+	return m_InvalidTexture;
+}
+
+int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType)
+{
+	char aCompleteFilename[512];
+	unsigned char *pBuffer;
+	png_t Png; // ignore_convention
+
+	// open file for reading
+	png_init(0,0); // ignore_convention
+
+	IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename));
+	if(File)
+		io_close(File);
+	else
+	{
+		dbg_msg("game/png", "failed to open file. filename='%s'", pFilename);
+		return 0;
+	}
+
+	int Error = png_open_file(&Png, aCompleteFilename); // ignore_convention
+	if(Error != PNG_NO_ERROR)
+	{
+		dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename);
+		if(Error != PNG_FILE_ERROR)
+			png_close_file(&Png); // ignore_convention
+		return 0;
+	}
+
+	if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention
+	{
+		dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename);
+		png_close_file(&Png); // ignore_convention
+		return 0;
+	}
+
+	pBuffer = (unsigned char *)mem_alloc(Png.width * Png.height * Png.bpp, 1); // ignore_convention
+	png_get_data(&Png, pBuffer); // ignore_convention
+	png_close_file(&Png); // ignore_convention
+
+	pImg->m_Width = Png.width; // ignore_convention
+	pImg->m_Height = Png.height; // ignore_convention
+	if(Png.color_type == PNG_TRUECOLOR) // ignore_convention
+		pImg->m_Format = CImageInfo::FORMAT_RGB;
+	else if(Png.color_type == PNG_TRUECOLOR_ALPHA) // ignore_convention
+		pImg->m_Format = CImageInfo::FORMAT_RGBA;
+	pImg->m_pData = pBuffer;
+	return 1;
+}
+
+void CGraphics_Threaded::KickCommandBuffer()
+{
+	m_pBackend->RunBuffer(m_pCommandBuffer);
+
+	// swap buffer
+	m_CurrentCommandBuffer ^= 1;
+	m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer];
+	m_pCommandBuffer->Reset();
+}
+
+void CGraphics_Threaded::ScreenshotDirect(const char *pFilename)
+{
+	// add swap command
+	CImageInfo Image;
+	mem_zero(&Image, sizeof(Image));
+
+	CCommandBuffer::SCommand_Screenshot Cmd;
+	Cmd.m_pImage = &Image;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the buffer and wait for the result
+	KickCommandBuffer();
+	WaitForIdle();
+
+	if(Image.m_pData)
+	{
+		// find filename
+		char aWholePath[1024];
+		png_t Png; // ignore_convention
+
+		IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
+		if(File)
+			io_close(File);
+
+		// save png
+		char aBuf[256];
+		str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
+		m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
+		png_open_file_write(&Png, aWholePath); // ignore_convention
+		png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR, (unsigned char *)Image.m_pData); // ignore_convention
+		png_close_file(&Png); // ignore_convention
+
+		mem_free(Image.m_pData);
+	}
+}
+
+void CGraphics_Threaded::TextureSet(int TextureID)
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin");
+	m_State.m_Texture = TextureID;
+}
+
+void CGraphics_Threaded::Clear(float r, float g, float b)
+{
+	CCommandBuffer::SCommand_Clear Cmd;
+	Cmd.m_Color.r = r;
+	Cmd.m_Color.g = g;
+	Cmd.m_Color.b = b;
+	Cmd.m_Color.a = 0;
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+void CGraphics_Threaded::QuadsBegin()
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice");
+	m_Drawing = DRAWING_QUADS;
+
+	QuadsSetSubset(0,0,1,1);
+	QuadsSetRotation(0);
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_Threaded::QuadsEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin");
+	FlushVertices();
+	m_Drawing = 0;
+}
+
+void CGraphics_Threaded::QuadsSetRotation(float Angle)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin");
+	m_Rotation = Angle;
+}
+
+void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, int Num)
+{
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aColor[pArray[i].m_Index].r = pArray[i].m_R;
+		m_aColor[pArray[i].m_Index].g = pArray[i].m_G;
+		m_aColor[pArray[i].m_Index].b = pArray[i].m_B;
+		m_aColor[pArray[i].m_Index].a = pArray[i].m_A;
+	}
+}
+
+void CGraphics_Threaded::SetColor(float r, float g, float b, float a)
+{
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin");
+	CColorVertex Array[4] = {
+		CColorVertex(0, r, g, b, a),
+		CColorVertex(1, r, g, b, a),
+		CColorVertex(2, r, g, b, a),
+		CColorVertex(3, r, g, b, a)};
+	SetColorVertex(Array, 4);
+}
+
+void CGraphics_Threaded::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin");
+
+	m_aTexture[0].u = TlU;	m_aTexture[1].u = BrU;
+	m_aTexture[0].v = TlV;	m_aTexture[1].v = TlV;
+
+	m_aTexture[3].u = TlU;	m_aTexture[2].u = BrU;
+	m_aTexture[3].v = BrV;	m_aTexture[2].v = BrV;
+}
+
+void CGraphics_Threaded::QuadsSetSubsetFree(
+	float x0, float y0, float x1, float y1,
+	float x2, float y2, float x3, float y3)
+{
+	m_aTexture[0].u = x0; m_aTexture[0].v = y0;
+	m_aTexture[1].u = x1; m_aTexture[1].v = y1;
+	m_aTexture[2].u = x2; m_aTexture[2].v = y2;
+	m_aTexture[3].u = x3; m_aTexture[3].v = y3;
+}
+
+void CGraphics_Threaded::QuadsDraw(CQuadItem *pArray, int Num)
+{
+	for(int i = 0; i < Num; ++i)
+	{
+		pArray[i].m_X -= pArray[i].m_Width/2;
+		pArray[i].m_Y -= pArray[i].m_Height/2;
+	}
+
+	QuadsDrawTL(pArray, Num);
+}
+
+void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num)
+{
+	CCommandBuffer::SPoint Center;
+	Center.z = 0;
+
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X;
+		m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y;
+		m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1];
+
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[2];
+		m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[2];
+
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[3];
+		m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[3];
+
+		if(m_Rotation != 0)
+		{
+			Center.x = pArray[i].m_X + pArray[i].m_Width/2;
+			Center.y = pArray[i].m_Y + pArray[i].m_Height/2;
+
+			Rotate4(Center, &m_aVertices[m_NumVertices + 4*i]);
+		}
+	}
+
+	AddVertices(4*Num);
+}
+
+void CGraphics_Threaded::QuadsDrawFreeform(const CFreeformItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X0;
+		m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y0;
+		m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X1;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y1;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1];
+
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X3;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y3;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[3];
+		m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[3];
+
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X2;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y2;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[2];
+		m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[2];
+	}
+
+	AddVertices(4*Num);
+}
+
+void CGraphics_Threaded::QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText)
+{
+	float StartX = x;
+
+	QuadsBegin();
+	SetColor(r,g,b,a);
+
+	while(*pText)
+	{
+		char c = *pText;
+		pText++;
+
+		if(c == '\n')
+		{
+			x = StartX;
+			y += Size;
+		}
+		else
+		{
+			QuadsSetSubset(
+				(c%16)/16.0f,
+				(c/16)/16.0f,
+				(c%16)/16.0f+1.0f/16.0f,
+				(c/16)/16.0f+1.0f/16.0f);
+
+			CQuadItem QuadItem(x, y, Size, Size);
+			QuadsDrawTL(&QuadItem, 1);
+			x += Size/2;
+		}
+	}
+
+	QuadsEnd();
+}
+
+int CGraphics_Threaded::IssueInit()
+{
+	int Flags = 0;
+	if(g_Config.m_GfxFullscreen) Flags |= IGraphicsBackend::INITFLAG_FULLSCREEN;
+	if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC;
+	if(g_Config.m_DbgResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE;
+
+	return m_pBackend->Init("Teeworlds", g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags);
+}
+
+int CGraphics_Threaded::InitWindow()
+{
+	if(IssueInit() == 0)
+		return 0;
+
+	// try disabling fsaa
+	while(g_Config.m_GfxFsaaSamples)
+	{
+		g_Config.m_GfxFsaaSamples--;
+
+		if(g_Config.m_GfxFsaaSamples)
+			dbg_msg("gfx", "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples);
+		else
+			dbg_msg("gfx", "disabling FSAA and trying again");
+
+		if(IssueInit() == 0)
+			return 0;
+	}
+
+	// try lowering the resolution
+	if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480)
+	{
+		dbg_msg("gfx", "setting resolution to 640x480 and trying again");
+		g_Config.m_GfxScreenWidth = 640;
+		g_Config.m_GfxScreenHeight = 480;
+
+		if(IssueInit() == 0)
+			return 0;
+	}
+
+	dbg_msg("gfx", "out of ideas. failed to init graphics");
+
+	return -1;
+}
+
+int CGraphics_Threaded::Init()
+{
+	// fetch pointers
+	m_pStorage = Kernel()->RequestInterface<IStorage>();
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
+
+	// Set all z to -5.0f
+	for(int i = 0; i < MAX_VERTICES; i++)
+		m_aVertices[i].m_Pos.z = -5.0f;
+
+	// init textures
+	m_FirstFreeTexture = 0;
+	for(int i = 0; i < MAX_TEXTURES; i++)
+		m_aTextures[i].m_Next = i+1;
+	m_aTextures[MAX_TEXTURES-1].m_Next = -1;
+
+	m_pBackend = CreateGraphicsBackend();
+	if(InitWindow() != 0)
+		return -1;
+
+	// fetch final resolusion
+	m_ScreenWidth = g_Config.m_GfxScreenWidth;
+	m_ScreenHeight = g_Config.m_GfxScreenHeight;
+
+	// create command buffers
+	for(int i = 0; i < NUM_CMDBUFFERS; i++)
+		m_apCommandBuffers[i] = new CCommandBuffer(128*1024, 2*1024*1024);
+	m_pCommandBuffer = m_apCommandBuffers[0];
+
+	// create null texture, will get id=0
+	static const unsigned char aNullTextureData[] = {
+		0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff,
+		0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff,
+		0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff,
+		0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff,
+	};
+
+	m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE);
+	return 0;
+}
+
+void CGraphics_Threaded::Shutdown()
+{
+	// shutdown the backend
+	m_pBackend->Shutdown();
+	delete m_pBackend;
+	m_pBackend = 0x0;
+
+	// delete the command buffers
+	for(int i = 0; i < NUM_CMDBUFFERS; i++)
+		delete m_apCommandBuffers[i];
+}
+
+void CGraphics_Threaded::Minimize()
+{
+	m_pBackend->Minimize();
+}
+
+void CGraphics_Threaded::Maximize()
+{
+	// TODO: SDL
+	m_pBackend->Maximize();
+}
+
+int CGraphics_Threaded::WindowActive()
+{
+	return m_pBackend->WindowActive();
+}
+
+int CGraphics_Threaded::WindowOpen()
+{
+	return m_pBackend->WindowOpen();
+
+}
+
+void CGraphics_Threaded::TakeScreenshot(const char *pFilename)
+{
+	// TODO: screenshot support
+	char aDate[20];
+	str_timestamp(aDate, sizeof(aDate));
+	str_format(m_aScreenshotName, sizeof(m_aScreenshotName), "screenshots/%s_%s.png", pFilename?pFilename:"screenshot", aDate);
+	m_DoScreenshot = true;
+}
+
+void CGraphics_Threaded::Swap()
+{
+	// TODO: screenshot support
+	if(m_DoScreenshot)
+	{
+		ScreenshotDirect(m_aScreenshotName);
+		m_DoScreenshot = false;
+	}
+
+	// add swap command
+	CCommandBuffer::SCommand_Swap Cmd;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the command buffer
+	KickCommandBuffer();
+}
+
+// syncronization
+void CGraphics_Threaded::InsertSignal(semaphore *pSemaphore)
+{
+	CCommandBuffer::SCommand_Signal Cmd;
+	Cmd.m_pSemaphore = pSemaphore;
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+bool CGraphics_Threaded::IsIdle()
+{
+	return m_pBackend->IsIdle();
+}
+
+void CGraphics_Threaded::WaitForIdle()
+{
+	m_pBackend->WaitForIdle();
+}
+
+int CGraphics_Threaded::GetVideoModes(CVideoMode *pModes, int MaxModes)
+{
+	if(g_Config.m_GfxDisplayAllModes)
+	{
+		int Count = sizeof(g_aFakeModes)/sizeof(CVideoMode);
+		mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes));
+		if(MaxModes < Count)
+			Count = MaxModes;
+		return Count;
+	}
+
+	// add videomodes command
+	CImageInfo Image;
+	mem_zero(&Image, sizeof(Image));
+
+	int NumModes = 0;
+	CCommandBuffer::SCommand_VideoModes Cmd;
+	Cmd.m_pModes = pModes;
+	Cmd.m_MaxModes = MaxModes;
+	Cmd.m_pNumModes = &NumModes;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the buffer and wait for the result and return it
+	KickCommandBuffer();
+	WaitForIdle();
+	return NumModes;
+}
+
+extern IEngineGraphics *CreateEngineGraphicsThreaded() { return new CGraphics_Threaded(); }
diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h
new file mode 100644
index 00000000..51148f0f
--- /dev/null
+++ b/src/engine/client/graphics_threaded.h
@@ -0,0 +1,443 @@
+#pragma once
+
+#include <base/tl/threading.h>
+
+#include <engine/graphics.h>
+
+class CCommandBuffer
+{
+	class CBuffer
+	{
+		unsigned char *m_pData;
+		unsigned m_Size;
+		unsigned m_Used;
+	public:
+		CBuffer(unsigned BufferSize)
+		{
+			m_Size = BufferSize;
+			m_pData = new unsigned char[m_Size];
+			m_Used = 0;
+		}
+
+		~CBuffer()
+		{
+			delete [] m_pData;
+			m_pData = 0x0;
+			m_Used = 0;
+			m_Size = 0;
+		}
+
+		void Reset()
+		{
+			m_Used = 0;
+		}
+
+		void *Alloc(unsigned Requested)
+		{
+			if(Requested + m_Used > m_Size)
+				return 0;
+			void *pPtr = &m_pData[m_Used];
+			m_Used += Requested;
+			return pPtr;
+		}
+
+		unsigned char *DataPtr() { return m_pData; }
+		unsigned DataSize() { return m_Size; }
+		unsigned DataUsed() { return m_Used; }
+	};
+
+public:
+	CBuffer m_CmdBuffer;
+	CBuffer m_DataBuffer;
+
+	enum
+	{
+		MAX_TEXTURES=1024*4,
+	};
+
+	enum
+	{
+		// commadn groups
+		CMDGROUP_CORE = 0, // commands that everyone has to implement
+		CMDGROUP_PLATFORM = 10000, // commands specific to a platform
+
+		//
+		CMD_NOP = CMDGROUP_CORE,
+
+		//
+		CMD_RUNBUFFER,
+
+		// syncronization
+		CMD_SIGNAL,
+
+		// texture commands
+		CMD_TEXTURE_CREATE,
+		CMD_TEXTURE_DESTROY,
+		CMD_TEXTURE_UPDATE,
+
+		// rendering
+		CMD_CLEAR,
+		CMD_RENDER,
+
+		// swap
+		CMD_SWAP,
+
+		// misc
+		CMD_SCREENSHOT,
+		CMD_VIDEOMODES,
+
+	};
+
+	enum
+	{
+		TEXFORMAT_INVALID = 0,
+		TEXFORMAT_RGB,
+		TEXFORMAT_RGBA,
+		TEXFORMAT_ALPHA,
+
+		TEXFLAG_NOMIPMAPS = 1,
+	};
+
+	enum
+	{
+		INITFLAG_FULLSCREEN = 1,
+		INITFLAG_VSYNC = 2,
+		INITFLAG_RESIZABLE = 4,
+	};
+
+	enum
+	{
+		//
+		PRIMTYPE_INVALID = 0,
+		PRIMTYPE_LINES,	
+		PRIMTYPE_QUADS,
+	};
+
+	enum
+	{
+		BLEND_NONE = 0,
+		BLEND_ALPHA,
+		BLEND_ADDITIVE,
+	};
+
+	struct SPoint { float x, y, z; };
+	struct STexCoord { float u, v; };
+	struct SColor { float r, g, b, a; };
+
+	struct SVertex
+	{
+		SPoint m_Pos;
+		STexCoord m_Tex;
+		SColor m_Color;
+	};
+
+	struct SCommand
+	{
+	public:
+		SCommand(unsigned Cmd) : m_Cmd(Cmd), m_Size(0) {}
+		unsigned m_Cmd;
+		unsigned m_Size;
+	};
+
+	struct SState
+	{
+		int m_BlendMode;
+		int m_Texture;
+		SPoint m_ScreenTL;
+		SPoint m_ScreenBR;
+
+		// clip
+		bool m_ClipEnable;
+		int m_ClipX;
+		int m_ClipY;
+		int m_ClipW;
+		int m_ClipH;
+	};
+		
+	struct SCommand_Clear : public SCommand
+	{
+		SCommand_Clear() : SCommand(CMD_CLEAR) {}
+		SColor m_Color;
+	};
+		
+	struct SCommand_Signal : public SCommand
+	{
+		SCommand_Signal() : SCommand(CMD_SIGNAL) {}
+		semaphore *m_pSemaphore;
+	};
+
+	struct SCommand_RunBuffer : public SCommand
+	{
+		SCommand_RunBuffer() : SCommand(CMD_RUNBUFFER) {}
+		CCommandBuffer *m_pOtherBuffer;
+	};
+
+	struct SCommand_Render : public SCommand
+	{
+		SCommand_Render() : SCommand(CMD_RENDER) {}
+		SState m_State;
+		unsigned m_PrimType;
+		unsigned m_PrimCount;
+		SVertex *m_pVertices; // you should use the command buffer data to allocate vertices for this command
+	};
+
+	struct SCommand_Screenshot : public SCommand
+	{
+		SCommand_Screenshot() : SCommand(CMD_SCREENSHOT) {}
+		CImageInfo *m_pImage; // processor will fill this out, the one who adds this command must free the data as well
+	};
+
+	struct SCommand_VideoModes : public SCommand
+	{
+		SCommand_VideoModes() : SCommand(CMD_VIDEOMODES) {}
+
+		CVideoMode *m_pModes; // processor will fill this in
+		int m_MaxModes; // maximum of modes the processor can write to the m_pModes
+		int *m_pNumModes; // processor will write to this pointer
+	};
+
+	struct SCommand_Swap : public SCommand
+	{
+		SCommand_Swap() : SCommand(CMD_SWAP) {}
+	};
+
+	struct SCommand_Texture_Create : public SCommand
+	{
+		SCommand_Texture_Create() : SCommand(CMD_TEXTURE_CREATE) {}
+
+		// texture information
+		int m_Slot;
+
+		int m_Width;
+		int m_Height;
+		int m_Format;
+		int m_StoreFormat;
+		int m_Flags;
+		void *m_pData; // will be freed by the command processor
+	};
+
+	struct SCommand_Texture_Update : public SCommand
+	{
+		SCommand_Texture_Update() : SCommand(CMD_TEXTURE_UPDATE) {}
+
+		// texture information
+		int m_Slot;
+
+		int m_X;
+		int m_Y;
+		int m_Width;
+		int m_Height;
+		int m_Format;
+		void *m_pData; // will be freed by the command processor
+	};
+
+
+	struct SCommand_Texture_Destroy : public SCommand
+	{
+		SCommand_Texture_Destroy() : SCommand(CMD_TEXTURE_DESTROY) {}
+
+		// texture information
+		int m_Slot;
+	};
+	
+	//
+	CCommandBuffer(unsigned CmdBufferSize, unsigned DataBufferSize)
+	: m_CmdBuffer(CmdBufferSize), m_DataBuffer(DataBufferSize)
+	{
+	}
+
+	void *AllocData(unsigned WantedSize)
+	{
+		return m_DataBuffer.Alloc(WantedSize);
+	}
+
+	template<class T>
+	bool AddCommand(const T &Command)
+	{
+		// make sure that we don't do something stupid like ->AddCommand(&Cmd);
+		(void)static_cast<const SCommand *>(&Command);
+
+		// allocate and copy the command into the buffer
+		SCommand *pCmd = (SCommand *)m_CmdBuffer.Alloc(sizeof(Command));
+		if(!pCmd)
+			return false;
+		mem_copy(pCmd, &Command, sizeof(Command));
+		pCmd->m_Size = sizeof(Command);
+		return true;
+	}
+
+	SCommand *GetCommand(unsigned *pIndex)
+	{
+		if(*pIndex >= m_CmdBuffer.DataUsed())
+			return NULL;
+
+		SCommand *pCommand = (SCommand *)&m_CmdBuffer.DataPtr()[*pIndex];
+		*pIndex += pCommand->m_Size;
+		return pCommand;
+	}
+
+	void Reset()
+	{
+		m_CmdBuffer.Reset();
+		m_DataBuffer.Reset();
+	}
+};
+
+// interface for the graphics backend
+// all these functions are called on the main thread
+class IGraphicsBackend
+{
+public:
+	enum
+	{
+		INITFLAG_FULLSCREEN = 1,
+		INITFLAG_VSYNC = 2,
+		INITFLAG_RESIZABLE = 4,
+	};
+
+	virtual int Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags) = 0;
+	virtual int Shutdown() = 0;
+
+	virtual void Minimize() = 0;
+	virtual void Maximize() = 0;
+	virtual int WindowActive() = 0;
+	virtual int WindowOpen() = 0;
+
+	virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
+	virtual bool IsIdle() const = 0;
+	virtual void WaitForIdle() = 0;
+};
+
+class CGraphics_Threaded : public IEngineGraphics
+{
+	enum
+	{
+		NUM_CMDBUFFERS = 2,
+
+		MAX_VERTICES = 32*1024,
+		MAX_TEXTURES = 1024*4,
+		
+		DRAWING_QUADS=1,
+		DRAWING_LINES=2
+	};
+
+	CCommandBuffer::SState m_State;
+	IGraphicsBackend *m_pBackend;
+
+	CCommandBuffer *m_apCommandBuffers[NUM_CMDBUFFERS];
+	CCommandBuffer *m_pCommandBuffer;
+	unsigned m_CurrentCommandBuffer;
+
+	//
+	class IStorage *m_pStorage;
+	class IConsole *m_pConsole;
+
+	CCommandBuffer::SVertex m_aVertices[MAX_VERTICES];
+	int m_NumVertices;
+
+	CCommandBuffer::SColor m_aColor[4];
+	CCommandBuffer::STexCoord m_aTexture[4];
+
+	bool m_RenderEnable;
+
+	float m_Rotation;
+	int m_Drawing;
+	bool m_DoScreenshot;
+	char m_aScreenshotName[128];
+
+	int m_InvalidTexture;
+
+	struct CTexture
+	{
+		int m_State;
+		int m_MemSize;
+		int m_Flags;
+		int m_Next;
+	};
+
+	CTexture m_aTextures[MAX_TEXTURES];
+	int m_FirstFreeTexture;
+	int m_TextureMemoryUsage;
+
+	void FlushVertices();
+	void AddVertices(int Count);
+	void Rotate4(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints);
+
+	static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp);
+	static unsigned char *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData);
+
+	void KickCommandBuffer();
+
+	int IssueInit();
+	int InitWindow();
+public:
+	CGraphics_Threaded();
+
+	virtual void ClipEnable(int x, int y, int w, int h);
+	virtual void ClipDisable();
+
+	virtual void BlendNone();
+	virtual void BlendNormal();
+	virtual void BlendAdditive();
+
+	virtual int MemoryUsage() const;
+
+	virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY);
+	virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY);
+
+	virtual void LinesBegin();
+	virtual void LinesEnd();
+	virtual void LinesDraw(const CLineItem *pArray, int Num);
+
+	virtual int UnloadTexture(int Index);
+	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags);
+	virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData);
+
+	// simple uncompressed RGBA loaders
+	virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags);
+	virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType);
+
+	void ScreenshotDirect(const char *pFilename);
+
+	virtual void TextureSet(int TextureID);
+
+	virtual void Clear(float r, float g, float b);
+
+	virtual void QuadsBegin();
+	virtual void QuadsEnd();
+	virtual void QuadsSetRotation(float Angle);
+
+	virtual void SetColorVertex(const CColorVertex *pArray, int Num);
+	virtual void SetColor(float r, float g, float b, float a);
+
+	virtual void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV);
+	virtual void QuadsSetSubsetFree(
+		float x0, float y0, float x1, float y1,
+		float x2, float y2, float x3, float y3);
+
+	virtual void QuadsDraw(CQuadItem *pArray, int Num);
+	virtual void QuadsDrawTL(const CQuadItem *pArray, int Num);
+	virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num);
+	virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText);
+
+	virtual void Minimize();
+	virtual void Maximize();
+
+	virtual int WindowActive();
+	virtual int WindowOpen();
+
+	virtual int Init();
+	virtual void Shutdown();
+
+	virtual void TakeScreenshot(const char *pFilename);
+	virtual void Swap();
+
+	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes);
+
+	// syncronization
+	virtual void InsertSignal(semaphore *pSemaphore);
+	virtual bool IsIdle();
+	virtual void WaitForIdle();
+};
+
+extern IGraphicsBackend *CreateGraphicsBackend();
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index 87964003..4dda9da9 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -370,7 +370,6 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
 	}*/
 
 	pEntry->m_GotInfo = 1;
-	Sort();
 }
 
 CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
@@ -388,7 +387,7 @@ CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
 	pEntry->m_Info.m_NetAddr = Addr;
 
 	pEntry->m_Info.m_Latency = 999;
-	net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress));
+	net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), true);
 	str_copy(pEntry->m_Info.m_aName, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aName));
 
 	// check if it's a favorite
@@ -528,7 +527,7 @@ void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) cons
 	if(g_Config.m_Debug)
 	{
 		char aAddrStr[NETADDR_MAXSTRSIZE];
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
 		char aBuf[256];
 		str_format(aBuf, sizeof(aBuf),"requesting server info from %s", aAddrStr);
 		m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
@@ -669,7 +668,7 @@ void CServerBrowser::AddFavorite(const NETADDR &Addr)
 	if(g_Config.m_Debug)
 	{
 		char aAddrStr[NETADDR_MAXSTRSIZE];
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
 		char aBuf[256];
 		str_format(aBuf, sizeof(aBuf), "added fav, %s", aAddrStr);
 		m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
@@ -723,12 +722,11 @@ void CServerBrowser::ConfigSaveCallback(IConfig *pConfig, void *pUserData)
 {
 	CServerBrowser *pSelf = (CServerBrowser *)pUserData;
 
-	int i;
 	char aAddrStr[128];
 	char aBuffer[256];
-	for(i = 0; i < pSelf->m_NumFavoriteServers; i++)
+	for(int i = 0; i < pSelf->m_NumFavoriteServers; i++)
 	{
-		net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr), true);
 		str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr);
 		pConfig->WriteLine(aBuffer);
 	}
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
index 45404d18..3f3d1a5d 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -209,6 +209,12 @@ int CSound::Init()
 	if(!g_Config.m_SndEnable)
 		return 0;
 
+	if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
+	{
+		dbg_msg("gfx", "unable to init SDL audio: %s", SDL_GetError());
+		return -1;
+	}
+
 	m_MixingRate = g_Config.m_SndRate;
 
 	// Set 16-bit stereo audio at 22Khz
@@ -256,6 +262,7 @@ int CSound::Update()
 int CSound::Shutdown()
 {
 	SDL_CloseAudio();
+	SDL_QuitSubSystem(SDL_INIT_AUDIO);
 	lock_destroy(m_SoundLock);
 	return 0;
 }
diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp
index 4a3a2eb3..af06fc11 100644
--- a/src/engine/client/text.cpp
+++ b/src/engine/client/text.cpp
@@ -9,21 +9,11 @@
 	#include <windows.h>
 #endif
 
-#ifdef CONF_PLATFORM_MACOSX
-	#include <OpenGL/gl.h>
-	#include <OpenGL/glu.h>
-#else
-	#include <GL/gl.h>
-	#include <GL/glu.h>
-#endif
-
 // ft2 texture
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
 // TODO: Refactor: clean this up
-
-
 enum
 {
 	MAX_CHARACTERS = 64,
@@ -54,7 +44,7 @@ struct CFontSizeData
 	int m_FontSize;
 	FT_Face *m_pFace;
 
-	GLuint m_aTextures[2];
+	int m_aTextures[2];
 	int m_TextureWidth;
 	int m_TextureHeight;
 
@@ -107,7 +97,7 @@ class CTextRender : public IEngineTextRender
 	float m_TextOutlineB;
 	float m_TextOutlineA;
 
-	int m_FontTextureFormat;
+	//int m_FontTextureFormat;
 
 	CFont *m_pDefaultFont;
 
@@ -158,26 +148,25 @@ class CTextRender : public IEngineTextRender
 		void *pMem = mem_alloc(Width*Height, 1);
 		mem_zero(pMem, Width*Height);
 
-		if(pSizeData->m_aTextures[0] == 0)
-			glGenTextures(2, pSizeData->m_aTextures);
-		else
-			FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight*2;
+		for(int i = 0; i < 2; i++)
+		{
+			if(pSizeData->m_aTextures[i] != 0)
+			{
+				Graphics()->UnloadTexture(pSizeData->m_aTextures[i]);
+				FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight;
+				pSizeData->m_aTextures[i] = 0;
+			}
+
+			pSizeData->m_aTextures[i] = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_ALPHA, pMem, CImageInfo::FORMAT_ALPHA, IGraphics::TEXLOAD_NOMIPMAPS);
+			FontMemoryUsage += Width*Height;
+		}
 
 		pSizeData->m_NumXChars = Xchars;
 		pSizeData->m_NumYChars = Ychars;
 		pSizeData->m_TextureWidth = Width;
 		pSizeData->m_TextureHeight = Height;
 		pSizeData->m_CurrentCharacter = 0;
-
-		for(int i = 0; i < 2; i++)
-		{
-			glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[i]);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-			glTexImage2D(GL_TEXTURE_2D, 0, m_FontTextureFormat, Width, Height, 0, m_FontTextureFormat, GL_UNSIGNED_BYTE, pMem);
-			FontMemoryUsage += Width*Height;
-		}
-
+		
 		dbg_msg("", "pFont memory usage: %d", FontMemoryUsage);
 
 		mem_free(pMem);
@@ -254,11 +243,16 @@ class CTextRender : public IEngineTextRender
 		int x = (SlotID%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars);
 		int y = (SlotID/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars);
 
+		Graphics()->LoadTextureRawSub(pSizeData->m_aTextures[Texnum], x, y,
+			pSizeData->m_TextureWidth/pSizeData->m_NumXChars,
+			pSizeData->m_TextureHeight/pSizeData->m_NumYChars,
+			CImageInfo::FORMAT_ALPHA, pData);
+		/*
 		glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[Texnum]);
 		glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
 			pSizeData->m_TextureWidth/pSizeData->m_NumXChars,
 			pSizeData->m_TextureHeight/pSizeData->m_NumYChars,
-			m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);
+			m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);*/
 	}
 
 	// 32k of data used for rendering glyphs
@@ -285,7 +279,8 @@ class CTextRender : public IEngineTextRender
 					Oldest = i;
 			}
 
-			if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq())
+			if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq() &&
+				(pSizeData->m_NumXChars < MAX_CHARACTERS || pSizeData->m_NumYChars < MAX_CHARACTERS))
 			{
 				IncreaseTextureSize(pSizeData);
 				return GetSlot(pSizeData);
@@ -455,7 +450,7 @@ public:
 		m_pDefaultFont = 0;
 
 		// GL_LUMINANCE can be good for debugging
-		m_FontTextureFormat = GL_ALPHA;
+		//m_FontTextureFormat = GL_ALPHA;
 	}
 
 	virtual void Init()
@@ -620,11 +615,10 @@ public:
 			if(pCursor->m_Flags&TEXTFLAG_RENDER)
 			{
 				// TODO: Make this better
-				glEnable(GL_TEXTURE_2D);
 				if (i == 0)
-					glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[1]);
+					Graphics()->TextureSet(pSizeData->m_aTextures[1]);
 				else
-					glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[0]);
+					Graphics()->TextureSet(pSizeData->m_aTextures[0]);
 
 				Graphics()->QuadsBegin();
 				if (i == 0)
diff --git a/src/engine/console.h b/src/engine/console.h
index 0abf4ad2..8951d2d1 100644
--- a/src/engine/console.h
+++ b/src/engine/console.h
@@ -78,6 +78,7 @@ public:
 
 	virtual bool LineIsValid(const char *pStr) = 0;
 	virtual void ExecuteLine(const char *Sptr) = 0;
+	virtual void ExecuteLineFlag(const char *Sptr, int FlasgMask) = 0;
 	virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0;
 	virtual void ExecuteFile(const char *pFilename) = 0;
 
diff --git a/src/engine/external/pnglite/VERSION b/src/engine/external/pnglite/VERSION
new file mode 100644
index 00000000..44a7df27
--- /dev/null
+++ b/src/engine/external/pnglite/VERSION
@@ -0,0 +1 @@
+0.1.17
\ No newline at end of file
diff --git a/src/engine/external/wavpack/VERSION b/src/engine/external/wavpack/VERSION
new file mode 100644
index 00000000..4e21c89e
--- /dev/null
+++ b/src/engine/external/wavpack/VERSION
@@ -0,0 +1 @@
+4.40
\ No newline at end of file
diff --git a/src/engine/graphics.h b/src/engine/graphics.h
index e1652cbe..6d31060e 100644
--- a/src/engine/graphics.h
+++ b/src/engine/graphics.h
@@ -5,6 +5,8 @@
 
 #include "kernel.h"
 
+#include <base/tl/threading.h>
+
 class CImageInfo
 {
 public:
@@ -55,7 +57,8 @@ public:
 	*/
 	enum
 	{
-		TEXLOAD_NORESAMPLE=1,
+		TEXLOAD_NORESAMPLE = 1,
+		TEXLOAD_NOMIPMAPS = 2,
 	};
 
 	int ScreenWidth() const { return m_ScreenWidth; }
@@ -80,6 +83,7 @@ public:
 	virtual int UnloadTexture(int Index) = 0;
 	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags) = 0;
 	virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) = 0;
+	virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData) = 0;
 	virtual void TextureSet(int TextureID) = 0;
 
 	struct CLineItem
@@ -131,13 +135,18 @@ public:
 	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes) = 0;
 
 	virtual void Swap() = 0;
+
+	// syncronization
+	virtual void InsertSignal(semaphore *pSemaphore) = 0;
+	virtual bool IsIdle() = 0;
+	virtual void WaitForIdle() = 0;
 };
 
 class IEngineGraphics : public IGraphics
 {
 	MACRO_INTERFACE("enginegraphics", 0)
 public:
-	virtual bool Init() = 0;
+	virtual int Init() = 0;
 	virtual void Shutdown() = 0;
 
 	virtual void Minimize() = 0;
@@ -149,5 +158,6 @@ public:
 };
 
 extern IEngineGraphics *CreateEngineGraphics();
+extern IEngineGraphics *CreateEngineGraphicsThreaded();
 
 #endif
diff --git a/src/engine/server.h b/src/engine/server.h
index f3c9ca7c..2e4a2f25 100644
--- a/src/engine/server.h
+++ b/src/engine/server.h
@@ -55,6 +55,12 @@ public:
 
 	virtual void SnapSetStaticsize(int ItemType, int Size) = 0;
 
+	enum
+	{
+		RCON_CID_SERV=-1,
+		RCON_CID_VOTE=-2,
+	};
+	virtual void SetRconCID(int ClientID) = 0;
 	virtual bool IsAuthed(int ClientID) = 0;
 	virtual void Kick(int ClientID, const char *pReason) = 0;
 
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index a904f466..de84ad35 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -19,6 +19,7 @@
 #include <engine/shared/econ.h>
 #include <engine/shared/filecollection.h>
 #include <engine/shared/mapchecker.h>
+#include <engine/shared/netban.h>
 #include <engine/shared/network.h>
 #include <engine/shared/packer.h>
 #include <engine/shared/protocol.h>
@@ -35,37 +36,48 @@
 	#include <windows.h>
 #endif
 
-static const char *StrLtrim(const char *pStr)
+static const char *StrUTF8Ltrim(const char *pStr)
 {
-	while(*pStr && *pStr >= 0 && *pStr <= 32)
-		pStr++;
-	return pStr;
-}
-
-static void StrRtrim(char *pStr)
-{
-	int i = str_length(pStr);
-	while(i >= 0)
+	while(*pStr)
 	{
-		if(pStr[i] < 0 || pStr[i] > 32)
-			break;
-		pStr[i] = 0;
-		i--;
+		const char *pStrOld = pStr;
+		int Code = str_utf8_decode(&pStr);
+
+		// check if unicode is not empty
+		if(Code > 0x20 && Code != 0xA0 && Code != 0x034F && (Code < 0x2000 || Code > 0x200F) && (Code < 0x2028 || Code > 0x202F) &&
+			(Code < 0x205F || Code > 0x2064) && (Code < 0x206A || Code > 0x206F) && (Code < 0xFE00 || Code > 0xFE0F) &&
+			Code != 0xFEFF && (Code < 0xFFF9 || Code > 0xFFFC))
+		{
+			return pStrOld;
+		}
 	}
+	return pStr;
 }
 
-
-static int StrAllnum(const char *pStr)
+static void StrUTF8Rtrim(char *pStr)
 {
-	while(*pStr)
+	const char *p = pStr;
+	const char *pEnd = 0;
+	while(*p)
 	{
-		if(!(*pStr >= '0' && *pStr <= '9'))
-			return 0;
-		pStr++;
+		const char *pStrOld = p;
+		int Code = str_utf8_decode(&p);
+
+		// check if unicode is not empty
+		if(Code > 0x20 && Code != 0xA0 && Code != 0x034F && (Code < 0x2000 || Code > 0x200F) && (Code < 0x2028 || Code > 0x202F) &&
+			(Code < 0x205F || Code > 0x2064) && (Code < 0x206A || Code > 0x206F) && (Code < 0xFE00 || Code > 0xFE0F) &&
+			Code != 0xFEFF && (Code < 0xFFF9 || Code > 0xFFFC))
+		{
+			pEnd = 0;
+		}
+		else if(pEnd == 0)
+			pEnd = pStrOld;
 	}
-	return 1;
+	if(pEnd != 0)
+		*(const_cast<char *>(pEnd)) = 0;
 }
 
+
 CSnapIDPool::CSnapIDPool()
 {
 	Reset();
@@ -154,6 +166,115 @@ void CSnapIDPool::FreeID(int ID)
 	}
 }
 
+
+void CServerBan::Init(IConsole *pConsole, IStorage *pStorage, CServer* pServer)
+{
+	CNetBan::Init(pConsole, pStorage);
+
+	m_pServer = pServer;
+
+	// overwrites base command, todo: improve this
+	Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBanExt, this, "Ban player with ip/client id for x minutes for any reason");
+}
+
+template<class T>
+int CServerBan::BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
+{
+	// validate address
+	if(Server()->m_RconClientID >= 0 && Server()->m_RconClientID < MAX_CLIENTS &&
+		Server()->m_aClients[Server()->m_RconClientID].m_State != CServer::CClient::STATE_EMPTY)
+	{
+		if(NetMatch(pData, Server()->m_NetServer.ClientAddr(Server()->m_RconClientID)))
+		{
+			Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (you can't ban yourself)");
+			return -1;
+		}
+
+		for(int i = 0; i < MAX_CLIENTS; ++i)
+		{
+			if(i == Server()->m_RconClientID || Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
+				continue;
+
+			if(Server()->m_aClients[i].m_Authed >= Server()->m_RconAuthLevel && NetMatch(pData, Server()->m_NetServer.ClientAddr(i)))
+			{
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (command denied)");
+				return -1;
+			}
+		}
+	}
+	else if(Server()->m_RconClientID == IServer::RCON_CID_VOTE)
+	{
+		for(int i = 0; i < MAX_CLIENTS; ++i)
+		{
+			if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
+				continue;
+
+			if(Server()->m_aClients[i].m_Authed != CServer::AUTHED_NO && NetMatch(pData, Server()->m_NetServer.ClientAddr(i)))
+			{
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (command denied)");
+				return -1;
+			}
+		}
+	}
+
+	int Result = Ban(pBanPool, pData, Seconds, pReason);
+	if(Result != 0)
+		return Result;
+
+	// drop banned clients
+	typename T::CDataType Data = *pData;
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
+			continue;
+
+		if(NetMatch(&Data, Server()->m_NetServer.ClientAddr(i)))
+		{
+			CNetHash NetHash(&Data);
+			char aBuf[256];
+			MakeBanInfo(pBanPool->Find(&Data, &NetHash), aBuf, sizeof(aBuf), MSGTYPE_PLAYER);
+			Server()->m_NetServer.Drop(i, aBuf);
+		}
+	}
+
+	return Result;
+}
+
+int CServerBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
+{
+	return BanExt(&m_BanAddrPool, pAddr, Seconds, pReason);
+}
+
+int CServerBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
+{
+	if(pRange->IsValid())
+		return BanExt(&m_BanRangePool, pRange, Seconds, pReason);
+
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+void CServerBan::ConBanExt(IConsole::IResult *pResult, void *pUser)
+{
+	CServerBan *pThis = static_cast<CServerBan *>(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";
+
+	if(StrAllnum(pStr))
+	{
+		int ClientID = str_toint(pStr);
+		if(ClientID < 0 || ClientID >= MAX_CLIENTS || pThis->Server()->m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY)
+			pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid client id)");
+		else
+			pThis->BanAddr(pThis->Server()->m_NetServer.ClientAddr(ClientID), Minutes*60, pReason);
+	}
+	else
+		ConBan(pResult, pUser);
+}
+
+
 void CServer::CClient::Reset()
 {
 	// reset input
@@ -183,7 +304,7 @@ CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta)
 
 	m_MapReload = 0;
 
-	m_RconClientID = -1;
+	m_RconClientID = IServer::RCON_CID_SERV;
 	m_RconAuthLevel = AUTHED_ADMIN;
 
 	Init();
@@ -195,8 +316,8 @@ int CServer::TrySetClientName(int ClientID, const char *pName)
 	char aTrimmedName[64];
 
 	// trim the name
-	str_copy(aTrimmedName, StrLtrim(pName), sizeof(aTrimmedName));
-	StrRtrim(aTrimmedName);
+	str_copy(aTrimmedName, StrUTF8Ltrim(pName), sizeof(aTrimmedName));
+	StrUTF8Rtrim(aTrimmedName);
 
 	// check if new and old name are the same
 	if(m_aClients[ClientID].m_aName[0] && str_comp(m_aClients[ClientID].m_aName, aTrimmedName) == 0)
@@ -324,6 +445,11 @@ int CServer::Init()
 	return 0;
 }
 
+void CServer::SetRconCID(int ClientID)
+{
+	m_RconClientID = ClientID;
+}
+
 bool CServer::IsAuthed(int ClientID)
 {
 	return m_aClients[ClientID].m_Authed;
@@ -346,11 +472,7 @@ int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo)
 void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size)
 {
 	if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME)
-	{
-		NETADDR Addr = m_NetServer.ClientAddr(ClientID);
-		Addr.port = 0;
-		net_addr_str(&Addr, pAddrStr, Size);
-	}
+		net_addr_str(m_NetServer.ClientAddr(ClientID), pAddrStr, Size, false);
 }
 
 
@@ -596,9 +718,8 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
 {
 	CServer *pThis = (CServer *)pUser;
 
-	NETADDR Addr = pThis->m_NetServer.ClientAddr(ClientID);
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	net_addr_str(pThis->m_NetServer.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, "server", aBuf);
@@ -692,7 +813,6 @@ void CServer::UpdateClientRconCommands()
 void CServer::ProcessClientPacket(CNetChunk *pPacket)
 {
 	int ClientID = pPacket->m_ClientID;
-	NETADDR Addr;
 	CUnpacker Unpacker;
 	Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
 
@@ -771,9 +891,8 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 		{
 			if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTING)
 			{
-				Addr = m_NetServer.ClientAddr(ClientID);
 				char aAddrStr[NETADDR_MAXSTRSIZE];
-				net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 
 				char aBuf[256];
 				str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s", ClientID, aAddrStr);
@@ -787,9 +906,8 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 		{
 			if(m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID))
 			{
-				Addr = m_NetServer.ClientAddr(ClientID);
 				char aAddrStr[NETADDR_MAXSTRSIZE];
-				net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 
 				char aBuf[256];
 				str_format(aBuf, sizeof(aBuf), "player has entered the game. ClientID=%x addr=%s", ClientID, aAddrStr);
@@ -862,9 +980,9 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 				m_RconClientID = ClientID;
 				m_RconAuthLevel = m_aClients[ClientID].m_Authed;
 				Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD);
-				Console()->ExecuteLine(pCmd);
+				Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER);
 				Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN);
-				m_RconClientID = -1;
+				m_RconClientID = IServer::RCON_CID_SERV;
 				m_RconAuthLevel = AUTHED_ADMIN;
 			}
 		}
@@ -923,10 +1041,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 						if(!g_Config.m_SvRconBantime)
 							m_NetServer.Drop(ClientID, "Too many remote console authentication tries");
 						else
-						{
-							NETADDR Addr = m_NetServer.ClientAddr(ClientID);
-							BanAdd(Addr, g_Config.m_SvRconBantime*60, "Too many remote console authentication tries");
-						}
+							m_ServerBan.BanAddr(m_NetServer.ClientAddr(ClientID), g_Config.m_SvRconBantime*60, "Too many remote console authentication tries");
 					}
 				}
 				else
@@ -970,7 +1085,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
 	}
 }
 
-void CServer::SendServerInfo(NETADDR *pAddr, int Token)
+void CServer::SendServerInfo(const NETADDR *pAddr, int Token)
 {
 	CNetChunk Packet;
 	CPacker p;
@@ -1039,38 +1154,10 @@ void CServer::UpdateServerInfo()
 	for(int i = 0; i < MAX_CLIENTS; ++i)
 	{
 		if(m_aClients[i].m_State != CClient::STATE_EMPTY)
-		{
-			NETADDR Addr = m_NetServer.ClientAddr(i);
-			SendServerInfo(&Addr, -1);
-		}
+			SendServerInfo(m_NetServer.ClientAddr(i), -1);
 	}
 }
 
-int CServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason)
-{
-	Addr.port = 0;
-	char aAddrStr[128];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-	char aBuf[256];
-	if(Seconds)
-		str_format(aBuf, sizeof(aBuf), "banned %s for %d minutes", aAddrStr, Seconds/60);
-	else
-		str_format(aBuf, sizeof(aBuf), "banned %s for life", aAddrStr);
-	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-
-	return m_NetServer.BanAdd(Addr, Seconds, pReason);
-}
-
-int CServer::BanRemove(NETADDR Addr)
-{
-	return m_NetServer.BanRemove(Addr);
-}
-
-int CServer::BanRemoveAll()
-{
-	return m_NetServer.BanRemoveAll();
-}
-
 
 void CServer::PumpNetwork()
 {
@@ -1097,6 +1184,7 @@ void CServer::PumpNetwork()
 			ProcessClientPacket(&Packet);
 	}
 
+	m_ServerBan.Update();
 	m_Econ.Update();
 }
 
@@ -1195,7 +1283,7 @@ int CServer::Run()
 		BindAddr.port = g_Config.m_SvPort;
 	}
 
-	if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0))
+	if(!m_NetServer.Open(BindAddr, &m_ServerBan, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0))
 	{
 		dbg_msg("server", "couldn't open socket. port might already be in use");
 		return -1;
@@ -1203,7 +1291,8 @@ int CServer::Run()
 
 	m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this);
 
-	m_Econ.Init(Console());
+	m_ServerBan.Init(Console(), Storage(), this);
+	m_Econ.Init(Console(), &m_ServerBan);
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName);
@@ -1366,170 +1455,23 @@ void CServer::ConKick(IConsole::IResult *pResult, void *pUser)
 		((CServer *)pUser)->Kick(pResult->GetInteger(0), "Kicked by console");
 }
 
-void CServer::ConBan(IConsole::IResult *pResult, void *pUser)
-{
-	NETADDR Addr;
-	CServer *pServer = (CServer *)pUser;
-	const char *pStr = pResult->GetString(0);
-	int Minutes = 30;
-	const char *pReason = "No reason given";
-
-	if(pResult->NumArguments() > 1)
-		Minutes = max(0, pResult->GetInteger(1));
-
-	if(pResult->NumArguments() > 2)
-		pReason = pResult->GetString(2);
-
-	if(net_addr_from_str(&Addr, pStr) == 0)
-	{
-		if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && pServer->m_aClients[pServer->m_RconClientID].m_State != CClient::STATE_EMPTY)
-		{
-			NETADDR AddrCheck = pServer->m_NetServer.ClientAddr(pServer->m_RconClientID);
-			Addr.port = AddrCheck.port = 0;
-			if(net_addr_comp(&Addr, &AddrCheck) == 0)
-			{
-				pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself");
-				return;
-			}
-
-			for(int i = 0; i < MAX_CLIENTS; ++i)
-			{
-				if(i == pServer->m_RconClientID)
-					continue;
-
-				AddrCheck = pServer->m_NetServer.ClientAddr(i);
-				AddrCheck.port = 0;
-				if(net_addr_comp(&Addr, &AddrCheck) == 0 && pServer->m_aClients[i].m_Authed > pServer->m_RconAuthLevel)
-				{
-					pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied");
-					return;
-				}
-			}
-		}
-		pServer->BanAdd(Addr, Minutes*60, pReason);
-	}
-	else if(StrAllnum(pStr))
-	{
-		int ClientID = str_toint(pStr);
-
-		if(ClientID < 0 || ClientID >= MAX_CLIENTS || pServer->m_aClients[ClientID].m_State == CClient::STATE_EMPTY)
-		{
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid client id");
-			return;
-		}
-		else if(pServer->m_RconClientID == ClientID)
-		{
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself");
-			return;
-		}
-		else if(pServer->m_aClients[ClientID].m_Authed > pServer->m_RconAuthLevel)
-		{
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied");
-			return;
-		}
-
-		Addr = pServer->m_NetServer.ClientAddr(ClientID);
-		pServer->BanAdd(Addr, Minutes*60, pReason);
-	}
-	else
-	{
-		pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid network address to ban");
-		return;
- 	}
-}
-
-void CServer::ConUnban(IConsole::IResult *pResult, void *pUser)
-{
-	NETADDR Addr;
-	CServer *pServer = (CServer *)pUser;
-	const char *pStr = pResult->GetString(0);
-
-	if(net_addr_from_str(&Addr, pStr) == 0 && !pServer->BanRemove(Addr))
-	{
-		char aAddrStr[NETADDR_MAXSTRSIZE];
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-
-		char aBuf[256];
-		str_format(aBuf, sizeof(aBuf), "unbanned %s", aAddrStr);
-		pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-	}
-	else if(StrAllnum(pStr))
-	{
-		int BanIndex = str_toint(pStr);
-		CNetServer::CBanInfo Info;
-		if(BanIndex < 0 || !pServer->m_NetServer.BanGet(BanIndex, &Info))
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid ban index");
-		else if(!pServer->BanRemove(Info.m_Addr))
-		{
-			char aAddrStr[NETADDR_MAXSTRSIZE];
-			net_addr_str(&Info.m_Addr, aAddrStr, sizeof(aAddrStr));
-
-			char aBuf[256];
-			str_format(aBuf, sizeof(aBuf), "unbanned %s", aAddrStr);
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
-		}
-	}
-	else
-		pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "invalid network address");
-}
-
-void CServer::ConUnbanAll(IConsole::IResult *pResult, void *pUser)
-{
-	CServer *pServer = (CServer *)pUser;
-	if(!pServer->BanRemoveAll())
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "unbanned all");
-}
-
-void CServer::ConBans(IConsole::IResult *pResult, void *pUser)
-{
-	unsigned Now = time_timestamp();
-	char aBuf[1024];
-	char aAddrStr[NETADDR_MAXSTRSIZE];
-	CServer* pServer = (CServer *)pUser;
-
-	int Num = pServer->m_NetServer.BanNum();
-	for(int i = 0; i < Num; i++)
-	{
-		CNetServer::CBanInfo Info;
-		pServer->m_NetServer.BanGet(i, &Info);
-		NETADDR Addr = Info.m_Addr;
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-
-		if(Info.m_Expires == -1)
-		{
-			str_format(aBuf, sizeof(aBuf), "#%i %s for life", i, aAddrStr);
-		}
-		else
-		{
-			unsigned t = Info.m_Expires - Now;
-			str_format(aBuf, sizeof(aBuf), "#%i %s for %d minutes and %d seconds", i, aAddrStr, t/60, t%60);
-		}
-		pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
-	}
-	str_format(aBuf, sizeof(aBuf), "%d ban(s)", Num);
-	pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
-}
-
 void CServer::ConStatus(IConsole::IResult *pResult, void *pUser)
 {
-	int i;
-	NETADDR Addr;
 	char aBuf[1024];
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	CServer* pServer = (CServer *)pUser;
+	CServer* pThis = static_cast<CServer *>(pUser);
 
-	for(i = 0; i < MAX_CLIENTS; i++)
+	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		if(pServer->m_aClients[i].m_State != CClient::STATE_EMPTY)
+		if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY)
 		{
-			Addr = pServer->m_NetServer.ClientAddr(i);
-			net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
-			if(pServer->m_aClients[i].m_State == CClient::STATE_INGAME)
+			net_addr_str(pThis->m_NetServer.ClientAddr(i), aAddrStr, sizeof(aAddrStr), true);
+			if(pThis->m_aClients[i].m_State == CClient::STATE_INGAME)
 				str_format(aBuf, sizeof(aBuf), "id=%d addr=%s name='%s' score=%d", i, aAddrStr,
-					pServer->m_aClients[i].m_aName, pServer->m_aClients[i].m_Score);
+					pThis->m_aClients[i].m_aName, pThis->m_aClients[i].m_Score);
 			else
 				str_format(aBuf, sizeof(aBuf), "id=%d addr=%s connecting", i, aAddrStr);
-			pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
+			pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "Server", aBuf);
 		}
 	}
 }
@@ -1584,6 +1526,27 @@ void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser)
 	((CServer *)pUser)->m_MapReload = 1;
 }
 
+void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
+{
+	CServer *pServer = (CServer *)pUser;
+
+	if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS &&
+		pServer->m_aClients[pServer->m_RconClientID].m_State != CServer::CClient::STATE_EMPTY)
+	{
+		CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS);
+		Msg.AddInt(0);	//authed
+		Msg.AddInt(0);	//cmdlist
+		pServer->SendMsgEx(&Msg, MSGFLAG_VITAL, pServer->m_RconClientID, true);
+
+		pServer->m_aClients[pServer->m_RconClientID].m_Authed = AUTHED_NO;
+		pServer->m_aClients[pServer->m_RconClientID].m_pRconCmdToSend = 0;
+		pServer->SendRconLine(pServer->m_RconClientID, "Logout successful.");
+		char aBuf[32];
+		str_format(aBuf, sizeof(aBuf), "ClientID=%d logged out", pServer->m_RconClientID);
+		pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+	}
+}
+
 void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
 {
 	pfnCallback(pResult, pCallbackUserData);
@@ -1642,12 +1605,9 @@ void CServer::RegisterCommands()
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
 
 	Console()->Register("kick", "i?r", CFGFLAG_SERVER, ConKick, this, "Kick player with specified id for any reason");
-	Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, "Ban player with ip/id for x minutes for any reason");
-	Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, "Unban ip");
-	Console()->Register("unban_all", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnbanAll, this, "Clear all bans");
-	Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, "Show banlist");
 	Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, "List players");
 	Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, "Shut down");
+	Console()->Register("logout", "", CFGFLAG_SERVER, ConLogout, this, "Logout of rcon");
 
 	Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, "Record to a file");
 	Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, "Stop recording");
@@ -1708,7 +1668,7 @@ int main(int argc, const char **argv) // ignore_convention
 	IEngine *pEngine = CreateEngine("Teeworlds");
 	IEngineMap *pEngineMap = CreateEngineMap();
 	IGameServer *pGameServer = CreateGameServer();
-	IConsole *pConsole = CreateConsole(CFGFLAG_SERVER);
+	IConsole *pConsole = CreateConsole(CFGFLAG_SERVER|CFGFLAG_ECON);
 	IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer();
 	IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention
 	IConfig *pConfig = CreateConfig();
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index 4c450a48..e32f6733 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -5,6 +5,7 @@
 
 #include <engine/server.h>
 
+
 class CSnapIDPool
 {
 	enum
@@ -39,6 +40,25 @@ public:
 	void FreeID(int ID);
 };
 
+
+class CServerBan : public CNetBan
+{
+	class CServer *m_pServer;
+
+	template<class T> int BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
+
+public:
+	class CServer *Server() const { return m_pServer; }
+
+	void Init(class IConsole *pConsole, class IStorage *pStorage, class CServer* pServer);
+
+	int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
+	int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
+
+	static void ConBanExt(class IConsole::IResult *pResult, void *pUser);
+};
+
+
 class CServer : public IServer
 {
 	class IGameServer *m_pGameServer;
@@ -114,6 +134,7 @@ public:
 	CSnapIDPool m_IDPool;
 	CNetServer m_NetServer;
 	CEcon m_Econ;
+	CServerBan m_ServerBan;
 
 	IEngineMap *m_pMap;
 
@@ -156,6 +177,7 @@ public:
 
 	int Init();
 
+	void SetRconCID(int ClientID);
 	bool IsAuthed(int ClientID);
 	int GetClientInfo(int ClientID, CClientInfo *pInfo);
 	void GetClientAddr(int ClientID, char *pAddrStr, int Size);
@@ -184,13 +206,9 @@ public:
 
 	void ProcessClientPacket(CNetChunk *pPacket);
 
-	void SendServerInfo(NETADDR *pAddr, int Token);
+	void SendServerInfo(const NETADDR *pAddr, int Token);
 	void UpdateServerInfo();
 
-	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
-	int BanRemove(NETADDR Addr);
-	int BanRemoveAll();
-
 	void PumpNetwork();
 
 	char *GetMapName();
@@ -200,15 +218,12 @@ public:
 	int Run();
 
 	static void ConKick(IConsole::IResult *pResult, void *pUser);
-	static void ConBan(IConsole::IResult *pResult, void *pUser);
-	static void ConUnban(IConsole::IResult *pResult, void *pUser);
-	static void ConUnbanAll(IConsole::IResult *pResult, void *pUser);
-	static void ConBans(IConsole::IResult *pResult, void *pUser);
- 	static void ConStatus(IConsole::IResult *pResult, void *pUser);
+	static void ConStatus(IConsole::IResult *pResult, void *pUser);
 	static void ConShutdown(IConsole::IResult *pResult, void *pUser);
 	static void ConRecord(IConsole::IResult *pResult, void *pUser);
 	static void ConStopRecord(IConsole::IResult *pResult, void *pUser);
 	static void ConMapReload(IConsole::IResult *pResult, void *pUser);
+	static void ConLogout(IConsole::IResult *pResult, void *pUser);
 	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);
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 c812063a..d92073d7 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -70,6 +70,9 @@ 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")
 
@@ -89,12 +92,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(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 de5116c1..31c2281d 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)
 {
@@ -633,7 +643,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 +655,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,7 +668,13 @@ 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;
 
@@ -669,7 +685,8 @@ void CConsole::Register(const char *pName, const char *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)
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
index 6989c696..61798c37 100644
--- a/src/engine/shared/console.h
+++ b/src/engine/shared/console.h
@@ -156,7 +156,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 +170,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/econ.cpp b/src/engine/shared/econ.cpp
index 617cdbd6..e85bbd9b 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,18 +126,15 @@ 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");
 				}
 			}
 		}
@@ -135,7 +143,9 @@ void CEcon::Update()
 			char aFormatted[256];
 			str_format(aFormatted, sizeof(aBuf), "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 eb63bab5..95482639 100644
--- a/src/engine/shared/masterserver.cpp
+++ b/src/engine/shared/masterserver.cpp
@@ -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..eebe7c84
--- /dev/null
+++ b/src/engine/shared/netban.cpp
@@ -0,0 +1,615 @@
+#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>::Find(const T *pData, const CNetHash *pNetHash) const
+{
+	for(CBan<T> *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;
+}
+
+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_ip %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 %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..a93cc797
--- /dev/null
+++ b/src/engine/shared/netban.h
@@ -0,0 +1,184 @@
+#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;
+		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 ca460d67..dd43389e 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();
@@ -244,59 +242,29 @@ public:
 // server side
 class CNetServer
 {
-public:
-	struct CBanInfo
-	{
-		NETADDR m_Addr;
-		int m_Expires;
-		char m_Reason[128];
-	};
-
-private:
 	struct CSlot
 	{
 	public:
 		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;
-
 	NETFUNC_NEWCLIENT m_pfnNewClient;
 	NETFUNC_DELCLIENT m_pfnDelClient;
 	void *m_UserPtr;
 
 	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();
 
 	//
@@ -307,16 +275,10 @@ public:
 	//
 	int Drop(int ClientID, const char *pReason);
 
-	// banning
-	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
-	int BanRemove(NETADDR Addr);
-	int BanRemoveAll();
-	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; }
+	class CNetBan *NetBan() const { return m_pNetBan; }
 	int NetType() const { return m_Socket.type; }
 	int MaxClients() const { return m_MaxClients; }
 
@@ -326,27 +288,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;
@@ -359,7 +307,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();
 
 	//
@@ -371,10 +319,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 cfa081a2..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);
@@ -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_server.cpp b/src/engine/shared/network_server.cpp
index b4986bf0..1264a4a5 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -1,32 +1,14 @@
 /* (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 "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>
 
-#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));
@@ -36,6 +18,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)
@@ -48,8 +32,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);
 
-	BanRemoveAll();
-
 	return true;
 }
 
@@ -85,181 +67,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::BanRemoveAll()
-{
-	// clear bans memory
-	mem_zero(m_aBans, sizeof(m_aBans));
-	mem_zero(m_BanPool, sizeof(m_BanPool));
-	m_BanPool_FirstFree = 0;
-	m_BanPool_FirstUsed = 0;
-
-	// 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 0;
-}
-
-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();
@@ -267,13 +76,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;
 }
 
@@ -282,8 +84,6 @@ int CNetServer::Update()
 */
 int CNetServer::Recv(CNetChunk *pChunk)
 {
-	unsigned Now = time_timestamp();
-
 	while(1)
 	{
 		NETADDR Addr;
@@ -301,36 +101,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;
 			}
 
@@ -348,16 +124,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;
 						}
 					}
@@ -374,7 +149,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))
 							{
@@ -392,7 +167,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);
@@ -412,8 +187,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/game/client/components/console.cpp b/src/game/client/components/console.cpp
index f2e9e65d..d16d56cd 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -612,16 +612,10 @@ void CGameConsole::Dump(int Type)
 	IOHANDLE io = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
 	if(io)
 	{
-		#if defined(CONF_FAMILY_WINDOWS)
-			static const char Newline[] = "\r\n";
-		#else
-			static const char Newline[] = "\n";
-		#endif
-
 		for(CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.First(); pEntry; pEntry = pConsole->m_Backlog.Next(pEntry))
 		{
 			io_write(io, pEntry->m_aText, str_length(pEntry->m_aText));
-			io_write(io, Newline, sizeof(Newline)-1);
+			io_write_newline(io);
 		}
 		io_close(io);
 	}
diff --git a/src/game/client/components/damageind.cpp b/src/game/client/components/damageind.cpp
index cba47666..9be6722a 100644
--- a/src/game/client/components/damageind.cpp
+++ b/src/game/client/components/damageind.cpp
@@ -38,7 +38,7 @@ void CDamageInd::Create(vec2 Pos, vec2 Dir)
 	if (i)
 	{
 		i->m_Pos = Pos;
-		i->m_Life = 0.75f;
+		i->m_StartTime = Client()->LocalTime();
 		i->m_Dir = Dir*-1;
 		i->m_StartAngle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
 	}
@@ -46,19 +46,19 @@ void CDamageInd::Create(vec2 Pos, vec2 Dir)
 
 void CDamageInd::OnRender()
 {
+
 	Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
 	Graphics()->QuadsBegin();
 	for(int i = 0; i < m_NumItems;)
 	{
-		vec2 Pos = mix(m_aItems[i].m_Pos+m_aItems[i].m_Dir*75.0f, m_aItems[i].m_Pos, clamp((m_aItems[i].m_Life-0.60f)/0.15f, 0.0f, 1.0f));
-
-		m_aItems[i].m_Life -= Client()->FrameTime();
-		if(m_aItems[i].m_Life < 0.0f)
+		float Life = 0.75f - (Client()->LocalTime() - m_aItems[i].m_StartTime);
+		if(Life < 0.0f)
 			DestroyI(&m_aItems[i]);
 		else
 		{
-			Graphics()->SetColor(1.0f,1.0f,1.0f, m_aItems[i].m_Life/0.1f);
-			Graphics()->QuadsSetRotation(m_aItems[i].m_StartAngle + m_aItems[i].m_Life * 2.0f);
+			vec2 Pos = mix(m_aItems[i].m_Pos+m_aItems[i].m_Dir*75.0f, m_aItems[i].m_Pos, clamp((Life-0.60f)/0.15f, 0.0f, 1.0f));
+			Graphics()->SetColor(1.0f,1.0f,1.0f, Life/0.1f);
+			Graphics()->QuadsSetRotation(m_aItems[i].m_StartAngle + Life * 2.0f);
 			RenderTools()->SelectSprite(SPRITE_STAR1);
 			RenderTools()->DrawSprite(Pos.x, Pos.y, 48.0f);
 			i++;
@@ -66,3 +66,6 @@ void CDamageInd::OnRender()
 	}
 	Graphics()->QuadsEnd();
 }
+
+
+
diff --git a/src/game/client/components/damageind.h b/src/game/client/components/damageind.h
index 2f89e422..4a5975a8 100644
--- a/src/game/client/components/damageind.h
+++ b/src/game/client/components/damageind.h
@@ -12,7 +12,7 @@ class CDamageInd : public CComponent
 	{
 		vec2 m_Pos;
 		vec2 m_Dir;
-		float m_Life;
+		float m_StartTime;
 		float m_StartAngle;
 	};
 
diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp
index 17365116..df3afdea 100644
--- a/src/game/client/components/hud.cpp
+++ b/src/game/client/components/hud.cpp
@@ -248,7 +248,7 @@ void CHud::RenderFps()
 	if(g_Config.m_ClShowfps)
 	{
 		// calculate avg. fps
-		float FPS = 1.0f / Client()->FrameTime();
+		float FPS = 1.0f / Client()->RenderFrameTime();
 		m_AverageFPS = (m_AverageFPS*(1.0f-(1.0f/m_AverageFPS))) + (FPS*(1.0f/m_AverageFPS));
 		char Buf[512];
 		str_format(Buf, sizeof(Buf), "%d", (int)m_AverageFPS);
diff --git a/src/game/client/components/items.cpp b/src/game/client/components/items.cpp
index 1c338994..9f787a4b 100644
--- a/src/game/client/components/items.cpp
+++ b/src/game/client/components/items.cpp
@@ -22,7 +22,6 @@ void CItems::OnReset()
 
 void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 {
-
 	// get positions
 	float Curvature = 0;
 	float Speed = 0;
@@ -64,7 +63,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 	if(pCurrent->m_Type == WEAPON_GRENADE)
 	{
 		m_pClient->m_pEffects->SmokeTrail(Pos, Vel*-1);
-		m_pClient->m_pFlow->Add(Pos, Vel*1000*Client()->FrameTime(), 10.0f);
 
 		if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
 		{
@@ -85,7 +83,6 @@ void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID)
 	else
 	{
 		m_pClient->m_pEffects->BulletTrail(Pos);
-		m_pClient->m_pFlow->Add(Pos, Vel*1000*Client()->FrameTime(), 10.0f);
 
 		if(length(Vel) > 0.00001f)
 			Graphics()->QuadsSetRotation(GetAngle(Vel));
diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp
index a1e629a6..f0460d26 100644
--- a/src/game/client/components/maplayers.cpp
+++ b/src/game/client/components/maplayers.cpp
@@ -188,16 +188,11 @@ void CMapLayers::OnRender()
 				IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
 				if(File)
 				{
-					#if defined(CONF_FAMILY_WINDOWS)
-						static const char Newline[] = "\r\n";
-					#else
-						static const char Newline[] = "\n";
-					#endif
 					for(int y = 0; y < pTMap->m_Height; y++)
 					{
 						for(int x = 0; x < pTMap->m_Width; x++)
 							io_write(File, &(pTiles[y*pTMap->m_Width + x].m_Index), sizeof(pTiles[y*pTMap->m_Width + x].m_Index));
-						io_write(File, Newline, sizeof(Newline)-1);
+						io_write_newline(File);
 					}
 					io_close(File);
 				}
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index a39139c8..0e61ec6e 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -627,6 +627,8 @@ int CMenus::RenderMenubar(CUIRect r)
 
 void CMenus::RenderLoading()
 {
+	// TODO: not supported right now due to separate render thread
+
 	static int64 LastLoadRender = 0;
 	float Percent = m_LoadCurrent++/(float)m_LoadTotal;
 
diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp
index 2fd1c2f3..6bcf21de 100644
--- a/src/game/client/gameclient.cpp
+++ b/src/game/client/gameclient.cpp
@@ -95,7 +95,6 @@ void CGameClient::OnConsoleInit()
 {
 	m_pEngine = Kernel()->RequestInterface<IEngine>();
 	m_pClient = Kernel()->RequestInterface<IClient>();
-	m_pGraphics = Kernel()->RequestInterface<IGraphics>();
 	m_pTextRender = Kernel()->RequestInterface<ITextRender>();
 	m_pSound = Kernel()->RequestInterface<ISound>();
 	m_pInput = Kernel()->RequestInterface<IInput>();
@@ -194,12 +193,10 @@ void CGameClient::OnConsoleInit()
 	Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, 0, 0, "Force a voting option");
 	Console()->Register("clear_votes", "", CFGFLAG_SERVER, 0, 0, "Clears the voting options");
 	Console()->Register("vote", "r", CFGFLAG_SERVER, 0, 0, "Force a vote to yes/no");
+	Console()->Register("swap_teams", "", CFGFLAG_SERVER, 0, 0, "Swap the current teams");
+	Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, 0, 0, "Shuffle the current teams");
 
 
-	// propagate pointers
-	m_UI.SetGraphics(Graphics(), TextRender());
-	m_RenderTools.m_pGraphics = Graphics();
-	m_RenderTools.m_pUI = UI();
 	for(int i = 0; i < m_All.m_Num; i++)
 		m_All.m_paComponents[i]->m_pClient = this;
 
@@ -223,6 +220,13 @@ void CGameClient::OnConsoleInit()
 
 void CGameClient::OnInit()
 {
+	m_pGraphics = Kernel()->RequestInterface<IGraphics>();
+
+	// propagate pointers
+	m_UI.SetGraphics(Graphics(), TextRender());
+	m_RenderTools.m_pGraphics = Graphics();
+	m_RenderTools.m_pUI = UI();
+	
 	int64 Start = time_get();
 
 	// set the language
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index dc993bfd..1f163983 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -204,7 +204,15 @@ void CGameContext::CreateSoundGlobal(int Sound, int Target)
 
 	CNetMsg_Sv_SoundGlobal Msg;
 	Msg.m_SoundID = Sound;
-	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, Target);
+	if(Target == -2)
+		Server()->SendPackMsg(&Msg, MSGFLAG_NOSEND, -1);
+	else
+	{
+		int Flag = MSGFLAG_VITAL;
+		if(Target != -1)
+			Flag |= MSGFLAG_NORECORD;
+		Server()->SendPackMsg(&Msg, Flag, Target);
+	}
 }
 
 
@@ -379,6 +387,22 @@ void CGameContext::SendTuningParams(int ClientID)
 	Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
 }
 
+void CGameContext::SwapTeams()
+{
+	if(!m_pController->IsTeamplay())
+		return;
+	
+	SendChat(-1, CGameContext::CHAT_ALL, "Teams were swapped");
+
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+			m_apPlayers[i]->SetTeam(m_apPlayers[i]->GetTeam()^1, false);
+	}
+
+	(void)m_pController->CheckTeamBalance();
+}
+
 void CGameContext::OnTick()
 {
 	// check tuning
@@ -457,7 +481,9 @@ void CGameContext::OnTick()
 
 			if(m_VoteEnforce == VOTE_ENFORCE_YES)
 			{
+				Server()->SetRconCID(IServer::RCON_CID_VOTE);
 				Console()->ExecuteLine(m_aVoteCommand);
+				Server()->SetRconCID(IServer::RCON_CID_SERV);
 				EndVote();
 				SendChat(-1, CGameContext::CHAT_ALL, "Vote passed");
 
@@ -713,7 +739,6 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
 				char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
 				Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr));
 				str_format(aCmd, sizeof(aCmd), "ban %s %d Banned by vote", aAddrStr, g_Config.m_SvVoteKickBantime);
-				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aCmd);
 			}
 		}
 		else if(str_comp_nocase(pMsg->m_Type, "spectate") == 0)
@@ -1036,17 +1061,14 @@ void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData)
 	CGameContext *pSelf = (CGameContext *)pUserData;
 	int ClientID = clamp(pResult->GetInteger(0), 0, (int)MAX_CLIENTS-1);
 	int Team = clamp(pResult->GetInteger(1), -1, 1);
-	int Delay = 0;
-	if(pResult->NumArguments() > 2)
-		Delay = pResult->GetInteger(2);
+	int Delay = pResult->NumArguments()>2 ? pResult->GetInteger(2) : 0;
+	if(!pSelf->m_apPlayers[ClientID])
+		return;
 
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "moved client %d to team %d", ClientID, Team);
 	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
 
-	if(!pSelf->m_apPlayers[ClientID])
-		return;
-
 	pSelf->m_apPlayers[ClientID]->m_TeamChangeTick = pSelf->Server()->Tick()+pSelf->Server()->TickSpeed()*Delay*60;
 	pSelf->m_apPlayers[ClientID]->SetTeam(Team);
 	(void)pSelf->m_pController->CheckTeamBalance();
@@ -1058,12 +1080,61 @@ void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData)
 	int Team = clamp(pResult->GetInteger(0), -1, 1);
 
 	char aBuf[256];
-	str_format(aBuf, sizeof(aBuf), "moved all clients to team %d", Team);
-	pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+	str_format(aBuf, sizeof(aBuf), "All players were moved to the %s", pSelf->m_pController->GetTeamName(Team));
+	pSelf->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
 
 	for(int i = 0; i < MAX_CLIENTS; ++i)
 		if(pSelf->m_apPlayers[i])
-			pSelf->m_apPlayers[i]->SetTeam(Team);
+			pSelf->m_apPlayers[i]->SetTeam(Team, false);
+
+	(void)pSelf->m_pController->CheckTeamBalance();
+}
+
+void CGameContext::ConSwapTeams(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->SwapTeams();
+}
+
+void CGameContext::ConShuffleTeams(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	if(!pSelf->m_pController->IsTeamplay())
+		return;
+
+	int CounterRed = 0;
+	int CounterBlue = 0;
+	int PlayerTeam = 0;
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+		if(pSelf->m_apPlayers[i] && pSelf->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+			++PlayerTeam;
+	PlayerTeam = (PlayerTeam+1)/2;
+	
+	pSelf->SendChat(-1, CGameContext::CHAT_ALL, "Teams were shuffled");
+
+	for(int i = 0; i < MAX_CLIENTS; ++i)
+	{
+		if(pSelf->m_apPlayers[i] && pSelf->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
+		{
+			if(CounterRed == PlayerTeam)
+				pSelf->m_apPlayers[i]->SetTeam(TEAM_BLUE, false);
+			else if(CounterBlue == PlayerTeam)
+				pSelf->m_apPlayers[i]->SetTeam(TEAM_RED, false);
+			else
+			{	
+				if(rand() % 2)
+				{
+					pSelf->m_apPlayers[i]->SetTeam(TEAM_BLUE, false);
+					++CounterBlue;
+				}
+				else
+				{
+					pSelf->m_apPlayers[i]->SetTeam(TEAM_RED, false);
+					++CounterRed;
+				}
+			}
+		}
+	}
 
 	(void)pSelf->m_pController->CheckTeamBalance();
 }
@@ -1327,6 +1398,8 @@ void CGameContext::OnConsoleInit()
 	Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "Say in chat");
 	Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team");
 	Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team");
+	Console()->Register("swap_teams", "", CFGFLAG_SERVER, ConSwapTeams, this, "Swap the current teams");
+	Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, ConShuffleTeams, this, "Shuffle the current teams");
 
 	Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option");
 	Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option");
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 6288850d..fe07637e 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -54,6 +54,8 @@ class CGameContext : public IGameServer
 	static void ConSay(IConsole::IResult *pResult, void *pUserData);
 	static void ConSetTeam(IConsole::IResult *pResult, void *pUserData);
 	static void ConSetTeamAll(IConsole::IResult *pResult, void *pUserData);
+	static void ConSwapTeams(IConsole::IResult *pResult, void *pUserData);
+	static void ConShuffleTeams(IConsole::IResult *pResult, void *pUserData);
 	static void ConAddVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConRemoveVote(IConsole::IResult *pResult, void *pUserData);
 	static void ConForceVote(IConsole::IResult *pResult, void *pUserData);
@@ -141,6 +143,9 @@ public:
 	void CheckPureTuning();
 	void SendTuningParams(int ClientID);
 
+	//
+	void SwapTeams();
+
 	// engine events
 	virtual void OnInit();
 	virtual void OnConsoleInit();
diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp
index 3d582991..5eb49c00 100644
--- a/src/game/server/gamecontroller.cpp
+++ b/src/game/server/gamecontroller.cpp
@@ -245,7 +245,11 @@ void IGameController::CycleMap()
 		return;
 
 	if(m_RoundCount < g_Config.m_SvRoundsPerMap-1)
+	{
+		if(g_Config.m_SvRoundSwap)
+			GameServer()->SwapTeams();
 		return;
+	}
 
 	// handle maprotation
 	const char *pMapRotation = g_Config.m_SvMaprotation;
diff --git a/src/game/server/gamemodes/ctf.cpp b/src/game/server/gamemodes/ctf.cpp
index 66cc4c2c..9e45c1fe 100644
--- a/src/game/server/gamemodes/ctf.cpp
+++ b/src/game/server/gamemodes/ctf.cpp
@@ -259,6 +259,8 @@ void CGameControllerCTF::Tick()
 						else
 							GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_PL, c);
 					}
+					// demo record entry
+					GameServer()->CreateSoundGlobal(SOUND_CTF_GRAB_EN, -2);
 					break;
 				}
 			}
diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp
index 38baad75..df9727a9 100644
--- a/src/game/server/player.cpp
+++ b/src/game/server/player.cpp
@@ -230,7 +230,7 @@ void CPlayer::Respawn()
 		m_Spawning = true;
 }
 
-void CPlayer::SetTeam(int Team)
+void CPlayer::SetTeam(int Team, bool DoChatMsg)
 {
 	// clamp the team
 	Team = GameServer()->m_pController->ClampTeam(Team);
@@ -238,8 +238,11 @@ void CPlayer::SetTeam(int Team)
 		return;
 
 	char aBuf[512];
-	str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(m_ClientID), GameServer()->m_pController->GetTeamName(Team));
-	GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
+	if(DoChatMsg)
+	{
+		str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(m_ClientID), GameServer()->m_pController->GetTeamName(Team));
+		GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
+	}
 
 	KillCharacter();
 
diff --git a/src/game/server/player.h b/src/game/server/player.h
index b8c4ce6c..dd804a95 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -20,7 +20,7 @@ public:
 
 	void TryRespawn();
 	void Respawn();
-	void SetTeam(int Team);
+	void SetTeam(int Team, bool DoChatMsg=true);
 	int GetTeam() const { return m_Team; };
 	int GetCID() const { return m_ClientID; };
 
diff --git a/src/game/variables.h b/src/game/variables.h
index 3a587ef3..4faa109d 100644
--- a/src/game/variables.h
+++ b/src/game/variables.h
@@ -60,6 +60,7 @@ MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day t
 MACRO_CONFIG_INT(SvTeamdamage, sv_teamdamage, 0, 0, 1, CFGFLAG_SERVER, "Team damage")
 MACRO_CONFIG_STR(SvMaprotation, sv_maprotation, 768, "", CFGFLAG_SERVER, "Maps to rotate between")
 MACRO_CONFIG_INT(SvRoundsPerMap, sv_rounds_per_map, 1, 1, 100, CFGFLAG_SERVER, "Number of rounds on each map before rotating")
+MACRO_CONFIG_INT(SvRoundSwap, sv_round_swap, 1, 0, 1, CFGFLAG_SERVER, "Swap teams between rounds")
 MACRO_CONFIG_INT(SvPowerups, sv_powerups, 1, 0, 1, CFGFLAG_SERVER, "Allow powerups like ninja")
 MACRO_CONFIG_INT(SvScorelimit, sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Score limit (0 disables)")
 MACRO_CONFIG_INT(SvTimelimit, sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes (0 disables)")
diff --git a/src/mastersrv/mastersrv.cpp b/src/mastersrv/mastersrv.cpp
index 70e4aecf..632a524c 100644
--- a/src/mastersrv/mastersrv.cpp
+++ b/src/mastersrv/mastersrv.cpp
@@ -1,20 +1,23 @@
 /* (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/shared/network.h>
-#include <engine/shared/config.h>
+
 #include <engine/console.h>
-#include <engine/storage.h>
 #include <engine/kernel.h>
+#include <engine/storage.h>
+
+#include <engine/shared/config.h>
+#include <engine/shared/netban.h>
+#include <engine/shared/network.h>
 
 #include "mastersrv.h"
 
+
 enum {
 	MTU = 1400,
 	MAX_SERVERS_PER_PACKET=75,
 	MAX_PACKETS=16,
 	MAX_SERVERS=MAX_SERVERS_PER_PACKET*MAX_PACKETS,
-	MAX_BANS=128,
 	EXPIRE_TIME = 90
 };
 
@@ -77,14 +80,7 @@ static CCountPacketData m_CountData;
 static CCountPacketData m_CountDataLegacy;
 
 
-struct CBanEntry
-{
-	NETADDR m_Address;
-	int64 m_Expire;
-};
-
-static CBanEntry m_aBans[MAX_BANS];
-static int m_NumBans = 0;
+CNetBan m_NetBan;
 
 static CNetClient m_NetChecker; // NAT/FW checker
 static CNetClient m_NetOp; // main
@@ -217,9 +213,9 @@ void AddCheckserver(NETADDR *pInfo, NETADDR *pAlt, ServerType Type)
 	}
 
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr));
+	net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true);
 	char aAltAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(pAlt, aAltAddrStr, sizeof(aAltAddrStr));
+	net_addr_str(pAlt, aAltAddrStr, sizeof(aAltAddrStr), true);
 	dbg_msg("mastersrv", "checking: %s (%s)", aAddrStr, aAltAddrStr);
 	m_aCheckServers[m_NumCheckServers].m_Address = *pInfo;
 	m_aCheckServers[m_NumCheckServers].m_AltAddress = *pAlt;
@@ -237,7 +233,7 @@ void AddServer(NETADDR *pInfo, ServerType Type)
 		if(net_addr_comp(&m_aServers[i].m_Address, pInfo) == 0)
 		{
 			char aAddrStr[NETADDR_MAXSTRSIZE];
-			net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr));
+			net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true);
 			dbg_msg("mastersrv", "updated: %s", aAddrStr);
 			m_aServers[i].m_Expire = time_get()+time_freq()*EXPIRE_TIME;
 			return;
@@ -252,7 +248,7 @@ void AddServer(NETADDR *pInfo, ServerType Type)
 	}
 
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr));
+	net_addr_str(pInfo, aAddrStr, sizeof(aAddrStr), true);
 	dbg_msg("mastersrv", "added: %s", aAddrStr);
 	m_aServers[m_NumServers].m_Address = *pInfo;
 	m_aServers[m_NumServers].m_Expire = time_get()+time_freq()*EXPIRE_TIME;
@@ -271,9 +267,9 @@ void UpdateServers()
 			if(m_aCheckServers[i].m_TryCount == 10)
 			{
 				char aAddrStr[NETADDR_MAXSTRSIZE];
-				net_addr_str(&m_aCheckServers[i].m_Address, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(&m_aCheckServers[i].m_Address, aAddrStr, sizeof(aAddrStr), true);
 				char aAltAddrStr[NETADDR_MAXSTRSIZE];
-				net_addr_str(&m_aCheckServers[i].m_AltAddress, aAltAddrStr, sizeof(aAltAddrStr));
+				net_addr_str(&m_aCheckServers[i].m_AltAddress, aAltAddrStr, sizeof(aAltAddrStr), true);
 				dbg_msg("mastersrv", "check failed: %s (%s)", aAddrStr, aAltAddrStr);
 
 				// FAIL!!
@@ -305,7 +301,7 @@ void PurgeServers()
 		{
 			// remove server
 			char aAddrStr[NETADDR_MAXSTRSIZE];
-			net_addr_str(&m_aServers[i].m_Address, aAddrStr, sizeof(aAddrStr));
+			net_addr_str(&m_aServers[i].m_Address, aAddrStr, sizeof(aAddrStr), true);
 			dbg_msg("mastersrv", "expired: %s", aAddrStr);
 			m_aServers[i] = m_aServers[m_NumServers-1];
 			m_NumServers--;
@@ -315,53 +311,9 @@ void PurgeServers()
 	}
 }
 
-bool CheckBan(NETADDR Addr)
-{
-	for(int i = 0; i < m_NumBans; i++)
-	{
-		if(net_addr_comp(&m_aBans[i].m_Address, &Addr) == 0)
-		{
-			return true;
-		}
-	}
-	Addr.port = 0;
-	for(int i = 0; i < m_NumBans; i++)
-	{
-		if(net_addr_comp(&m_aBans[i].m_Address, &Addr) == 0)
-		{
-			return true;
-		}
-	}
-	return false;
-}
-
-void ConAddBan(IConsole::IResult *pResult, void *pUser)
-{
-	if(m_NumBans == MAX_BANS)
-	{
-		dbg_msg("mastersrv", "error: banlist is full");
-		return;
-	}
-
-	if(net_addr_from_str(&m_aBans[m_NumBans].m_Address, pResult->GetString(0)) != 0)
-	{
-		dbg_msg("mastersrv", "error: invalid address");
-		return;
-	}
-
-	if(CheckBan(m_aBans[m_NumBans].m_Address))
-	{
-		dbg_msg("mastersrv", "duplicate ban: %s", pResult->GetString(0));
-		return;
-	}
-
-	dbg_msg("mastersrv", "ban added: %s", pResult->GetString(0));
-	m_NumBans++;
-}
-
 void ReloadBans()
 {
-	m_NumBans = 0;
+	m_NetBan.UnbanAll();
 	m_pConsole->ExecuteFile("master.cfg");
 }
 
@@ -398,7 +350,7 @@ int main(int argc, const char **argv) // ignore_convention
 	IStorage *pStorage = CreateStorage("Teeworlds", argc, argv);
 
 	m_pConsole = CreateConsole(CFGFLAG_MASTER);
-	m_pConsole->Register("ban", "s", CFGFLAG_MASTER, ConAddBan, 0, "Ban IP from mastersrv");
+	m_NetBan.Init(m_pConsole, pStorage);
 
 	bool RegisterFail = !pKernel->RegisterInterface(pStorage);
 	RegisterFail |= !pKernel->RegisterInterface(m_pConsole);
@@ -418,7 +370,8 @@ int main(int argc, const char **argv) // ignore_convention
 		while(m_NetOp.Recv(&Packet))
 		{
 			// check if the server is banned
-			if(CheckBan(Packet.m_Address)) continue;
+			if(m_NetBan.IsBanned(&Packet.m_Address, 0, 0))
+				continue;
 
 			if(Packet.m_DataSize == sizeof(SERVERBROWSE_HEARTBEAT)+2 &&
 				mem_comp(Packet.m_pData, SERVERBROWSE_HEARTBEAT, sizeof(SERVERBROWSE_HEARTBEAT)) == 0)
@@ -519,7 +472,8 @@ int main(int argc, const char **argv) // ignore_convention
 		while(m_NetChecker.Recv(&Packet))
 		{
 			// check if the server is banned
-			if(CheckBan(Packet.m_Address)) continue;
+			if(m_NetBan.IsBanned(&Packet.m_Address, 0, 0))
+				continue;
 
 			if(Packet.m_DataSize == sizeof(SERVERBROWSE_FWRESPONSE) &&
 				mem_comp(Packet.m_pData, SERVERBROWSE_FWRESPONSE, sizeof(SERVERBROWSE_FWRESPONSE)) == 0)
diff --git a/src/tools/crapnet.cpp b/src/tools/crapnet.cpp
index 98b6c0a7..fbd20445 100644
--- a/src/tools/crapnet.cpp
+++ b/src/tools/crapnet.cpp
@@ -129,7 +129,7 @@ void Run(int Port, NETADDR Dest)
 			if(m_ConfigLog)
 			{
 				char aAddrStr[NETADDR_MAXSTRSIZE];
-				net_addr_str(&From, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(&From, aAddrStr, sizeof(aAddrStr), true);
 				dbg_msg("crapnet", "<< %08d %s (%d)", p->m_ID, aAddrStr, p->m_DataSize);
 			}
 		}
@@ -193,7 +193,7 @@ void Run(int Port, NETADDR Dest)
 				if(m_ConfigLog)
 				{
 					char aAddrStr[NETADDR_MAXSTRSIZE];
-					net_addr_str(&p->m_SendTo, aAddrStr, sizeof(aAddrStr));
+					net_addr_str(&p->m_SendTo, aAddrStr, sizeof(aAddrStr), true);
 					dbg_msg("crapnet", ">> %08d %s (%d) %s", p->m_ID, aAddrStr, p->m_DataSize, aFlags);
 				}
 
diff --git a/src/tools/fake_server.cpp b/src/tools/fake_server.cpp
index 68c47659..9e76b3e7 100644
--- a/src/tools/fake_server.cpp
+++ b/src/tools/fake_server.cpp
@@ -113,7 +113,7 @@ static int Run()
 	int64 NextHeartBeat = 0;
 	NETADDR BindAddr = {NETTYPE_IPV4, {0},0};
 
-	if(!pNet->Open(BindAddr, 0, 0, 0))
+	if(!pNet->Open(BindAddr, 0, 0, 0, 0))
 		return 0;
 
 	while(1)
diff --git a/src/tools/tileset_borderadd.cpp b/src/tools/tileset_borderadd.cpp
new file mode 100644
index 00000000..25e2fa5e
--- /dev/null
+++ b/src/tools/tileset_borderadd.cpp
@@ -0,0 +1,86 @@
+/* (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/math.h>
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+typedef struct
+{
+	unsigned char r, g, b, a;
+} CPixel;
+
+static void TilesetBorderadd(int w, int h, CPixel *pSrc, CPixel *pDest)
+{
+	int TileW = w/16;
+	int TileH = h/16;
+
+	for(int tx = 0; tx < 16; tx++)
+	{
+		for(int ty = 0; ty < 16; ty++)
+		{
+			for(int x = 0; x < TileW + 4; x++)
+			{
+				for(int y = 0; y < TileH + 4; y++)
+				{
+					int SourceX = tx * TileW + clamp(x - 2, 0, TileW - 1);
+					int SourceY = ty * TileH + clamp(y - 2, 0, TileH - 1);
+					int DestX = tx * (TileW + 4) + x;
+					int DestY = ty * (TileH + 4) + y;
+
+					int SourceI = SourceY * w + SourceX;
+					int DestI = DestY * (w + 16 * 4) + DestX;
+
+					pDest[DestI] = pSrc[SourceI];
+
+				}
+			}
+		}
+	}
+}
+
+
+int FixFile(const char *pFileName)
+{
+	png_t Png;
+	CPixel *pBuffer[2] = {0,0};
+
+	png_init(0, 0);
+	png_open_file(&Png, pFileName);
+
+	if(Png.color_type != PNG_TRUECOLOR_ALPHA)
+	{
+		dbg_msg("tileset_borderadd", "%s: not an RGBA image", pFileName);
+		return 1;
+	}
+
+	int w = Png.width;
+	int h = Png.height;
+
+	pBuffer[0] = (CPixel*)mem_alloc(w*h*sizeof(CPixel), 1);
+	pBuffer[1] = (CPixel*)mem_alloc((w+16*4)*(h+16*4)*sizeof(CPixel), 1);
+	png_get_data(&Png, (unsigned char *)pBuffer[0]);
+	png_close_file(&Png);
+
+	TilesetBorderadd(w, h, pBuffer[0], pBuffer[1]);
+
+	// save here
+	png_open_file_write(&Png, pFileName);
+	png_set_data(&Png, w + 16 * 4, h + 16 * 4, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer[1]);
+	png_close_file(&Png);
+
+	return 0;
+}
+
+int main(int argc, const char **argv)
+{
+	dbg_logger_stdout();
+	if(argc == 1)
+	{
+		dbg_msg("Usage", "%s FILE1 [ FILE2... ]", argv[0]);
+		return -1;
+	}
+
+	for(int i = 1; i < argc; i++)
+		FixFile(argv[i]);
+	return 0;
+}
diff --git a/src/tools/tileset_borderrem.cpp b/src/tools/tileset_borderrem.cpp
new file mode 100644
index 00000000..d604ecbd
--- /dev/null
+++ b/src/tools/tileset_borderrem.cpp
@@ -0,0 +1,86 @@
+/* (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/math.h>
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+typedef struct
+{
+	unsigned char r, g, b, a;
+} CPixel;
+
+static void TilesetBorderrem(int w, int h, CPixel *pSrc, CPixel *pDest)
+{
+	int TileW = w/16;
+	int TileH = h/16;
+
+	for(int tx = 0; tx < 16; tx++)
+	{
+		for(int ty = 0; ty < 16; ty++)
+		{
+			for(int x = 0; x < TileW - 4; x++)
+			{
+				for(int y = 0; y < TileH - 4; y++)
+				{
+					int SourceX = tx * TileW + x + 2;
+					int SourceY = ty * TileH + y + 2;
+					int DestX = tx * (TileW - 4) + x;
+					int DestY = ty * (TileH - 4) + y;
+
+					int SourceI = SourceY * w + SourceX;
+					int DestI = DestY * (w - 16 * 4) + DestX;
+
+					pDest[DestI] = pSrc[SourceI];
+
+				}
+			}
+		}
+	}
+}
+
+
+int FixFile(const char *pFileName)
+{
+	png_t Png;
+	CPixel *pBuffer[2] = {0,0};
+
+	png_init(0, 0);
+	png_open_file(&Png, pFileName);
+
+	if(Png.color_type != PNG_TRUECOLOR_ALPHA)
+	{
+		dbg_msg("tileset_borderrem", "%s: not an RGBA image", pFileName);
+		return 1;
+	}
+
+	int w = Png.width;
+	int h = Png.height;
+
+	pBuffer[0] = (CPixel*)mem_alloc(w*h*sizeof(CPixel), 1);
+	pBuffer[1] = (CPixel*)mem_alloc((w-16*4)*(h-16*4)*sizeof(CPixel), 1);
+	png_get_data(&Png, (unsigned char *)pBuffer[0]);
+	png_close_file(&Png);
+
+	TilesetBorderrem(w, h, pBuffer[0], pBuffer[1]);
+
+	// save here
+	png_open_file_write(&Png, pFileName);
+	png_set_data(&Png, w - 16 * 4, h - 16 * 4, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer[1]);
+	png_close_file(&Png);
+
+	return 0;
+}
+
+int main(int argc, const char **argv)
+{
+	dbg_logger_stdout();
+	if(argc == 1)
+	{
+		dbg_msg("Usage", "%s FILE1 [ FILE2... ]", argv[0]);
+		return -1;
+	}
+
+	for(int i = 1; i < argc; i++)
+		FixFile(argv[i]);
+	return 0;
+}
diff --git a/src/tools/tileset_borderset.cpp b/src/tools/tileset_borderset.cpp
new file mode 100644
index 00000000..8b3e2996
--- /dev/null
+++ b/src/tools/tileset_borderset.cpp
@@ -0,0 +1,78 @@
+/* (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/math.h>
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+typedef struct
+{
+	unsigned char r, g, b, a;
+} CPixel;
+
+static void TilesetBorderset(int w, int h, CPixel *pSrc, CPixel *pDest)
+{
+	int TileW = w/16;
+	int TileH = h/16;
+
+	for(int tx = 0; tx < 16; tx++)
+	{
+		for(int ty = 0; ty < 16; ty++)
+		{
+			for(int x = 0; x < TileW; x++)
+			{
+				for(int y = 0; y < TileH; y++)
+				{
+					#define TILE_INDEX(tx_, ty_, x_, y_) (((ty_) * TileH + (y_)) * w + (tx_) * TileW + (x_))
+					pDest[TILE_INDEX(tx, ty, x, y)] = pSrc[TILE_INDEX(tx, ty, clamp(x, 2, TileW - 3), clamp(y, 2, TileH - 3))];
+				}
+			}
+		}
+	}
+}
+
+
+int FixFile(const char *pFileName)
+{
+	png_t Png;
+	CPixel *pBuffer[2] = {0,0};
+
+	png_init(0, 0);
+	png_open_file(&Png, pFileName);
+
+	if(Png.color_type != PNG_TRUECOLOR_ALPHA)
+	{
+		dbg_msg("tileset_borderset", "%s: not an RGBA image", pFileName);
+		return 1;
+	}
+
+	int w = Png.width;
+	int h = Png.height;
+
+	pBuffer[0] = (CPixel*)mem_alloc(w*h*sizeof(CPixel), 1);
+	pBuffer[1] = (CPixel*)mem_alloc(w*h*sizeof(CPixel), 1);
+	png_get_data(&Png, (unsigned char *)pBuffer[0]);
+	png_close_file(&Png);
+
+	TilesetBorderset(w, h, pBuffer[0], pBuffer[1]);
+
+	// save here
+	png_open_file_write(&Png, pFileName);
+	png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pBuffer[1]);
+	png_close_file(&Png);
+
+	return 0;
+}
+
+int main(int argc, const char **argv)
+{
+	dbg_logger_stdout();
+	if(argc == 1)
+	{
+		dbg_msg("Usage", "%s FILE1 [ FILE2... ]", argv[0]);
+		return -1;
+	}
+
+	for(int i = 1; i < argc; i++)
+		FixFile(argv[i]);
+	return 0;
+}