about summary refs log tree commit diff
path: root/src/engine/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/client')
-rw-r--r--src/engine/client/client.cpp2062
-rw-r--r--src/engine/client/client.h286
-rw-r--r--src/engine/client/ec_client.cpp2087
-rw-r--r--src/engine/client/ec_gfx.cpp992
-rw-r--r--src/engine/client/ec_gfx_text.cpp669
-rw-r--r--src/engine/client/ec_inp.cpp234
-rw-r--r--src/engine/client/ec_snd.cpp471
-rw-r--r--src/engine/client/ec_srvbrowse.cpp736
-rw-r--r--src/engine/client/editor.h10
-rw-r--r--src/engine/client/graphics.cpp938
-rw-r--r--src/engine/client/graphics.h180
-rw-r--r--src/engine/client/input.cpp208
-rw-r--r--src/engine/client/input.h37
-rw-r--r--src/engine/client/keynames.h524
-rw-r--r--src/engine/client/sound.cpp485
-rw-r--r--src/engine/client/sound.h39
-rw-r--r--src/engine/client/srvbrowse.cpp721
-rw-r--r--src/engine/client/srvbrowse.h111
-rw-r--r--src/engine/client/text.cpp718
19 files changed, 6250 insertions, 5258 deletions
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
new file mode 100644
index 00000000..dbcaa1ed
--- /dev/null
+++ b/src/engine/client/client.cpp
@@ -0,0 +1,2062 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+
+#include <stdlib.h> // qsort
+#include <stdarg.h>
+#include <math.h>
+
+#include <base/system.h>
+#include <engine/shared/engine.h>
+
+#include <engine/shared/protocol.h>
+#include <engine/shared/snapshot.h>
+#include <engine/shared/compression.h>
+#include <engine/shared/network.h>
+#include <engine/shared/config.h>
+#include <engine/shared/packer.h>
+#include <engine/shared/memheap.h>
+#include <engine/shared/datafile.h>
+#include <engine/shared/ringbuffer.h>
+#include <engine/shared/protocol.h>
+
+#include <engine/shared/demorec.h>
+
+#include <mastersrv/mastersrv.h>
+#include <versionsrv/versionsrv.h>
+
+#include "client.h"
+
+
+void CGraph::Init(float Min, float Max)
+{
+	m_Min = Min;
+	m_Max = Max;
+	m_Index = 0;
+}
+
+void CGraph::ScaleMax()
+{
+	int i = 0;
+	m_Max = 0;
+	for(i = 0; i < MAX_VALUES; i++)
+	{
+		if(m_aValues[i] > m_Max)
+			m_Max = m_aValues[i];
+	}
+}
+
+void CGraph::ScaleMin()
+{
+	int i = 0;
+	m_Min = m_Max;
+	for(i = 0; i < MAX_VALUES; i++)
+	{
+		if(m_aValues[i] < m_Min)
+			m_Min = m_aValues[i];
+	}
+}
+
+void CGraph::Add(float v, float r, float g, float b)
+{
+	m_Index = (m_Index+1)&(MAX_VALUES-1);
+	m_aValues[m_Index] = v;
+	m_aColors[m_Index][0] = r;
+	m_aColors[m_Index][1] = g;
+	m_aColors[m_Index][2] = b;
+}
+
+void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription)
+{
+	//m_pGraphics->BlendNormal();
+
+
+	pGraphics->TextureSet(-1);
+
+	pGraphics->QuadsBegin();
+	pGraphics->SetColor(0, 0, 0, 0.75f);
+	IGraphics::CQuadItem QuadItem(x, y, w, h);
+	pGraphics->QuadsDrawTL(&QuadItem, 1);
+	pGraphics->QuadsEnd();
+
+	pGraphics->LinesBegin();
+	pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f);
+	IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2);
+	pGraphics->LinesDraw(&LineItem, 1);
+	pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
+	IGraphics::CLineItem Array[2] = {
+		IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4),
+		IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)};
+	pGraphics->LinesDraw(Array, 2);
+	for(int i = 1; i < MAX_VALUES; i++)
+	{
+		float a0 = (i-1)/(float)MAX_VALUES;
+		float a1 = i/(float)MAX_VALUES;
+		int i0 = (m_Index+i-1)&(MAX_VALUES-1);
+		int i1 = (m_Index+i)&(MAX_VALUES-1);
+
+		float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min);
+		float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min);
+
+		IGraphics::CColorVertex Array[2] = {
+			IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f),
+			IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)};
+		pGraphics->SetColorVertex(Array, 2);
+		IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
+		pGraphics->LinesDraw(&LineItem, 1);
+
+	}
+	pGraphics->LinesEnd();
+
+	pGraphics->TextureSet(Font);
+	pGraphics->QuadsText(x+2, y+h-16, 16, 1,1,1,1, pDescription);
+
+	char aBuf[32];
+	str_format(aBuf, sizeof(aBuf), "%.2f", m_Max);
+	pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, 1,1,1,1, aBuf);
+
+	str_format(aBuf, sizeof(aBuf), "%.2f", m_Min);
+	pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, 1,1,1,1, aBuf);
+}
+
+
+void CSmoothTime::Init(int64 Target)
+{
+	m_Snap = time_get();
+	m_Current = Target;
+	m_Target = Target;
+	m_aAdjustSpeed[0] = 0.3f;
+	m_aAdjustSpeed[1] = 0.3f;
+	m_Graph.Init(0.0f, 0.5f);
+}
+
+void CSmoothTime::SetAdjustSpeed(int Direction, float Value)
+{
+	m_aAdjustSpeed[Direction] = Value;
+}
+
+int64 CSmoothTime::Get(int64 Now)
+{
+	int64 c = m_Current + (Now - m_Snap);
+	int64 t = m_Target + (Now - m_Snap);
+
+	// it's faster to adjust upward instead of downward
+	// we might need to adjust these abit
+
+	float AdjustSpeed = m_aAdjustSpeed[0];
+	if(t > c)
+		AdjustSpeed = m_aAdjustSpeed[1];
+
+	float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed;
+	if(a > 1.0f)
+		a = 1.0f;
+
+	int64 r = c + (int64)((t-c)*a);
+
+	m_Graph.Add(a+0.5f,1,1,1);
+
+	return r;
+}
+
+void CSmoothTime::UpdateInt(int64 Target)
+{
+	int64 Now = time_get();
+	m_Current = Get(Now);
+	m_Snap = Now;
+	m_Target = Target;
+}
+
+void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection)
+{
+	int UpdateTimer = 1;
+
+	if(TimeLeft < 0)
+	{
+		int IsSpike = 0;
+		if(TimeLeft < -50)
+		{
+			IsSpike = 1;
+
+			m_SpikeCounter += 5;
+			if(m_SpikeCounter > 50)
+				m_SpikeCounter = 50;
+		}
+
+		if(IsSpike && m_SpikeCounter < 15)
+		{
+			// ignore this ping spike
+			UpdateTimer = 0;
+			pGraph->Add(TimeLeft, 1,1,0);
+		}
+		else
+		{
+			pGraph->Add(TimeLeft, 1,0,0);
+			if(m_aAdjustSpeed[AdjustDirection] < 30.0f)
+				m_aAdjustSpeed[AdjustDirection] *= 2.0f;
+		}
+	}
+	else
+	{
+		if(m_SpikeCounter)
+			m_SpikeCounter--;
+
+		pGraph->Add(TimeLeft, 0,1,0);
+
+		m_aAdjustSpeed[AdjustDirection] *= 0.95f;
+		if(m_aAdjustSpeed[AdjustDirection] < 2.0f)
+			m_aAdjustSpeed[AdjustDirection] = 2.0f;
+	}
+
+	if(UpdateTimer)
+		UpdateInt(Target);
+}
+
+
+CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta)
+{
+	m_pEditor = 0;
+	m_pInput = 0;
+	m_pGraphics = 0;
+	m_pSound = 0;
+	m_pGameClient = 0;
+	m_pMap = 0;
+	m_pConsole = 0;
+
+	m_FrameTime = 0.0001f;
+	m_FrameTimeLow = 1.0f;
+	m_FrameTimeHigh = 0.0f;
+	m_Frames = 0;
+
+	m_GameTickSpeed = SERVER_TICK_SPEED;
+
+	m_WindowMustRefocus = 0;
+	m_SnapCrcErrors = 0;
+
+	m_AckGameTick = -1;
+	m_CurrentRecvTick = 0;
+	m_RconAuthed = 0;
+
+	// version-checking
+	m_aVersionStr[0] = '0';
+	m_aVersionStr[1] = 0;
+
+	// pinging
+	m_PingStartTime = 0;
+
+	//
+	m_aCurrentMap[0] = 0;
+	m_CurrentMapCrc = 0;
+
+	//
+	m_aCmdConnect[0] = 0;
+
+	// map download
+	m_aMapdownloadFilename[0] = 0;
+	m_aMapdownloadName[0] = 0;
+	m_MapdownloadFile = 0;
+	m_MapdownloadChunk = 0;
+	m_MapdownloadCrc = 0;
+	m_MapdownloadAmount = -1;
+	m_MapdownloadTotalsize = -1;
+
+	m_CurrentServerInfoRequestTime = -1;
+
+	m_CurrentInput = 0;
+
+	m_State = IClient::STATE_OFFLINE;
+	m_aServerAddressStr[0] = 0;
+
+	mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
+	m_RecivedSnapshots = 0;
+
+	m_VersionInfo.m_State = 0;
+}
+
+// ----- send functions -----
+int CClient::SendMsg(CMsgPacker *pMsg, int Flags)
+{
+	return SendMsgEx(pMsg, Flags, false);
+}
+
+int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System)
+{
+	CNetChunk Packet;
+
+	if(State() == IClient::STATE_OFFLINE)
+		return 0;
+
+	mem_zero(&Packet, sizeof(CNetChunk));
+
+	Packet.m_ClientID = 0;
+	Packet.m_pData = pMsg->Data();
+	Packet.m_DataSize = pMsg->Size();
+
+	// HACK: modify the message id in the packet and store the system flag
+	if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1)
+		dbg_break();
+
+	*((unsigned char*)Packet.m_pData) <<= 1;
+	if(System)
+		*((unsigned char*)Packet.m_pData) |= 1;
+
+	if(Flags&MSGFLAG_VITAL)
+		Packet.m_Flags |= NETSENDFLAG_VITAL;
+	if(Flags&MSGFLAG_FLUSH)
+		Packet.m_Flags |= NETSENDFLAG_FLUSH;
+
+	if(Flags&MSGFLAG_RECORD)
+	{
+		if(m_DemoRecorder.IsRecording())
+			m_DemoRecorder.RecordMessage(Packet.m_pData, Packet.m_DataSize);
+	}
+
+	if(!(Flags&MSGFLAG_NOSEND))
+		m_NetClient.Send(&Packet);
+	return 0;
+}
+
+void CClient::SendInfo()
+{
+	CMsgPacker Msg(NETMSG_INFO);
+	Msg.AddString(GameClient()->NetVersion(), 128);
+	Msg.AddString(g_Config.m_PlayerName, 128);
+	Msg.AddString(g_Config.m_ClanName, 128);
+	Msg.AddString(g_Config.m_Password, 128);
+	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
+}
+
+
+void CClient::SendEnterGame()
+{
+	CMsgPacker Msg(NETMSG_ENTERGAME);
+	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
+}
+
+void CClient::SendReady()
+{
+	CMsgPacker Msg(NETMSG_READY);
+	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
+}
+
+bool CClient::RconAuthed()
+{
+	return m_RconAuthed;
+}
+
+void CClient::RconAuth(const char *pName, const char *pPassword)
+{
+	if(RconAuthed())
+		return;
+        
+	CMsgPacker Msg(NETMSG_RCON_AUTH);
+	Msg.AddString(pName, 32);
+	Msg.AddString(pPassword, 32);
+	SendMsgEx(&Msg, MSGFLAG_VITAL);
+}
+
+void CClient::Rcon(const char *pCmd)
+{
+	CMsgPacker Msg(NETMSG_RCON_CMD);
+	Msg.AddString(pCmd, 256);
+	SendMsgEx(&Msg, MSGFLAG_VITAL);
+}
+
+bool CClient::ConnectionProblems()
+{
+	return m_NetClient.GotProblems() != 0;
+}
+
+void CClient::DirectInput(int *pInput, int Size)
+{
+	int i;
+	CMsgPacker Msg(NETMSG_INPUT);
+	Msg.AddInt(m_AckGameTick);
+	Msg.AddInt(m_PredTick);
+	Msg.AddInt(Size);
+
+	for(i = 0; i < Size/4; i++)
+		Msg.AddInt(pInput[i]);
+
+	SendMsgEx(&Msg, 0);
+}
+
+
+void CClient::SendInput()
+{
+	int64 Now = time_get();
+
+	if(m_PredTick <= 0)
+		return;
+
+	// fetch input
+	int Size = GameClient()->OnSnapInput(m_aInputs[m_CurrentInput].m_aData);
+
+	if(!Size)
+		return;
+
+	// pack input
+	CMsgPacker Msg(NETMSG_INPUT);
+	Msg.AddInt(m_AckGameTick);
+	Msg.AddInt(m_PredTick);
+	Msg.AddInt(Size);
+
+	m_aInputs[m_CurrentInput].m_Tick = m_PredTick;
+	m_aInputs[m_CurrentInput].m_PredictedTime = m_PredictedTime.Get(Now);
+	m_aInputs[m_CurrentInput].m_Time = Now;
+
+	// pack it
+	for(int i = 0; i < Size/4; i++)
+		Msg.AddInt(m_aInputs[m_CurrentInput].m_aData[i]);
+
+	m_CurrentInput++;
+	m_CurrentInput%=200;
+
+	SendMsgEx(&Msg, MSGFLAG_FLUSH);
+}
+
+const char *CClient::LatestVersion()
+{
+	return m_aVersionStr;
+}
+
+// TODO: OPT: do this alot smarter!
+int *CClient::GetInput(int Tick)
+{
+	int Best = -1;
+	for(int i = 0; i < 200; i++)
+	{
+		if(m_aInputs[i].m_Tick <= Tick && (Best == -1 || m_aInputs[Best].m_Tick < m_aInputs[i].m_Tick))
+			Best = i;
+	}
+
+	if(Best != -1)
+		return (int *)m_aInputs[Best].m_aData;
+	return 0;
+}
+
+// ------ state handling -----
+void CClient::SetState(int s)
+{
+	int Old = m_State;
+	if(g_Config.m_Debug)
+		dbg_msg("client", "state change. last=%d current=%d", m_State, s);
+	m_State = s;
+	if(Old != s)
+		GameClient()->OnStateChange(m_State, Old);
+}
+
+// called when the map is loaded and we should init for a new round
+void CClient::OnEnterGame()
+{
+	// reset input
+	int i;
+	for(i = 0; i < 200; i++)
+		m_aInputs[i].m_Tick = -1;
+	m_CurrentInput = 0;
+
+	// reset snapshots
+	m_aSnapshots[SNAP_CURRENT] = 0;
+	m_aSnapshots[SNAP_PREV] = 0;
+	m_SnapshotStorage.PurgeAll();
+	m_RecivedSnapshots = 0;
+	m_SnapshotParts = 0;
+	m_PredTick = 0;
+	m_CurrentRecvTick = 0;
+	m_CurGameTick = 0;
+	m_PrevGameTick = 0;
+}
+
+void CClient::EnterGame()
+{
+	if(State() == IClient::STATE_DEMOPLAYBACK)
+		return;
+
+	// now we will wait for two snapshots
+	// to finish the connection
+	SendEnterGame();
+	OnEnterGame();
+}
+
+void CClient::Connect(const char *pAddress)
+{
+	char aBuf[512];
+	const char *pPortStr = 0;
+	int Port = 8303;
+
+	Disconnect();
+
+	str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr));
+
+	dbg_msg("client", "connecting to '%s'", m_aServerAddressStr);
+
+	ServerInfoRequest();
+	str_copy(aBuf, m_aServerAddressStr, sizeof(aBuf));
+
+	for(int k = 0; aBuf[k]; k++)
+	{
+		if(aBuf[k] == ':')
+		{
+			pPortStr = &(aBuf[k+1]);
+			aBuf[k] = 0;
+			break;
+		}
+	}
+
+	if(pPortStr)
+		Port = str_toint(pPortStr);
+
+	// TODO: IPv6 support
+	if(net_host_lookup(aBuf, &m_ServerAddress, NETTYPE_IPV4) != 0)
+		dbg_msg("client", "could not find the address of %s, connecting to localhost", aBuf);
+
+	m_RconAuthed = 0;
+	m_ServerAddress.port = Port;
+	m_NetClient.Connect(&m_ServerAddress);
+	SetState(IClient::STATE_CONNECTING);
+
+	if(m_DemoRecorder.IsRecording())
+		m_DemoRecorder.Stop();
+
+	m_InputtimeMarginGraph.Init(-150.0f, 150.0f);
+	m_GametimeMarginGraph.Init(-150.0f, 150.0f);
+}
+
+void CClient::DisconnectWithReason(const char *pReason)
+{
+	// stop demo playback and recorder
+	m_DemoPlayer.Stop();
+	m_DemoRecorder.Stop();
+
+	//
+	m_RconAuthed = 0;
+	m_NetClient.Disconnect(pReason);
+	SetState(IClient::STATE_OFFLINE);
+	m_pMap->Unload();
+
+	// disable all downloads
+	m_MapdownloadChunk = 0;
+	if(m_MapdownloadFile)
+		io_close(m_MapdownloadFile);
+	m_MapdownloadFile = 0;
+	m_MapdownloadCrc = 0;
+	m_MapdownloadTotalsize = -1;
+	m_MapdownloadAmount = 0;
+
+	// clear the current server info
+	mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
+	mem_zero(&m_ServerAddress, sizeof(m_ServerAddress));
+
+	// clear snapshots
+	m_aSnapshots[SNAP_CURRENT] = 0;
+	m_aSnapshots[SNAP_PREV] = 0;
+	m_RecivedSnapshots = 0;
+}
+
+void CClient::Disconnect()
+{
+	DisconnectWithReason(0);
+}
+
+
+void CClient::GetServerInfo(CServerInfo *pServerInfo)
+{
+	mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
+}
+
+void CClient::ServerInfoRequest()
+{
+	mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
+	m_CurrentServerInfoRequestTime = 0;
+}
+
+int CClient::LoadData()
+{
+	m_DebugFont = Graphics()->LoadTexture("debug_font.png", CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE);
+	return 1;
+}
+
+// ---
+
+void *CClient::SnapGetItem(int SnapId, int Index, CSnapItem *pItem)
+{
+	CSnapshotItem *i;
+	dbg_assert(SnapId >= 0 && SnapId < NUM_SNAPSHOT_TYPES, "invalid SnapId");
+	i = m_aSnapshots[SnapId]->m_pAltSnap->GetItem(Index);
+	pItem->m_DataSize = m_aSnapshots[SnapId]->m_pAltSnap->GetItemSize(Index);
+	pItem->m_Type = i->Type();
+	pItem->m_Id = i->ID();
+	return (void *)i->Data();
+}
+
+void CClient::SnapInvalidateItem(int SnapId, int Index)
+{
+	CSnapshotItem *i;
+	dbg_assert(SnapId >= 0 && SnapId < NUM_SNAPSHOT_TYPES, "invalid SnapId");
+	i = m_aSnapshots[SnapId]->m_pAltSnap->GetItem(Index);
+	if(i)
+	{
+		if((char *)i < (char *)m_aSnapshots[SnapId]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[SnapId]->m_pAltSnap + m_aSnapshots[SnapId]->m_SnapSize)
+			dbg_msg("ASDFASDFASdf", "ASDFASDFASDF");
+		if((char *)i >= (char *)m_aSnapshots[SnapId]->m_pSnap && (char *)i < (char *)m_aSnapshots[SnapId]->m_pSnap + m_aSnapshots[SnapId]->m_SnapSize)
+			dbg_msg("ASDFASDFASdf", "ASDFASDFASDF");
+		i->m_TypeAndID = -1;
+	}
+}
+
+void *CClient::SnapFindItem(int SnapId, int Type, int Id)
+{
+	// TODO: linear search. should be fixed.
+	int i;
+
+	if(!m_aSnapshots[SnapId])
+		return 0x0;
+
+	for(i = 0; i < m_aSnapshots[SnapId]->m_pSnap->NumItems(); i++)
+	{
+		CSnapshotItem *pItem = m_aSnapshots[SnapId]->m_pAltSnap->GetItem(i);
+		if(pItem->Type() == Type && pItem->ID() == Id)
+			return (void *)pItem->Data();
+	}
+	return 0x0;
+}
+
+int CClient::SnapNumItems(int SnapId)
+{
+	dbg_assert(SnapId >= 0 && SnapId < NUM_SNAPSHOT_TYPES, "invalid SnapId");
+	if(!m_aSnapshots[SnapId])
+		return 0;
+	return m_aSnapshots[SnapId]->m_pSnap->NumItems();
+}
+
+void CClient::SnapSetStaticsize(int ItemType, int Size)
+{
+	m_SnapshotDelta.SetStaticsize(ItemType, Size);
+}
+
+
+void CClient::DebugRender()
+{
+	static NETSTATS Prev, Current;
+	static int64 LastSnap = 0;
+	static float FrameTimeAvg = 0;
+	int64 Now = time_get();
+	char aBuffer[512];
+
+	if(!g_Config.m_Debug)
+		return;
+
+	//m_pGraphics->BlendNormal();
+	Graphics()->TextureSet(m_DebugFont);
+	Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
+
+	if(time_get()-LastSnap > time_freq())
+	{
+		LastSnap = time_get();
+		Prev = Current;
+		net_stats(&Current);
+	}
+
+	/*
+		eth = 14
+		ip = 20
+		udp = 8
+		total = 42
+	*/
+	FrameTimeAvg = FrameTimeAvg*0.9f + m_FrameTime*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));
+	Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer);
+
+
+	{
+		int SendPackets = (Current.sent_packets-Prev.sent_packets);
+		int SendBytes = (Current.sent_bytes-Prev.sent_bytes);
+		int SendTotal = SendBytes + SendPackets*42;
+		int RecvPackets = (Current.recv_packets-Prev.recv_packets);
+		int RecvBytes = (Current.recv_bytes-Prev.recv_bytes);
+		int RecvTotal = RecvBytes + RecvPackets*42;
+
+		if(!SendPackets) SendPackets++;
+		if(!RecvPackets) RecvPackets++;
+		str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
+			SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets,
+			RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets);
+		Graphics()->QuadsText(2, 14, 16, 1,1,1,1, aBuffer);
+	}
+
+	// render rates
+	{
+		int y = 0;
+		int i;
+		for(i = 0; i < 256; i++)
+		{
+			if(m_SnapshotDelta.GetDataRate(i))
+			{
+				str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i),
+					(m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8);
+				Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, aBuffer);
+				y++;
+			}
+		}
+	}
+
+	str_format(aBuffer, sizeof(aBuffer), "pred: %d ms",
+		(int)((m_PredictedTime.Get(Now)-m_GameTime.Get(Now))*1000/(float)time_freq()));
+	Graphics()->QuadsText(2, 70, 16, 1,1,1,1, aBuffer);
+
+	// render graphs
+	if(g_Config.m_DbgGraphs)
+	{
+		//Graphics()->MapScreen(0,0,400.0f,300.0f);
+		float w = Graphics()->ScreenWidth()/4.0f;
+		float h = Graphics()->ScreenHeight()/6.0f;
+		float sp = Graphics()->ScreenWidth()/100.0f;
+		float x = Graphics()->ScreenWidth()-w-sp;
+
+		m_FpsGraph.ScaleMax();
+		m_FpsGraph.ScaleMin();
+		m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS");
+		m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin");
+		m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
+	}
+}
+
+void CClient::Quit()
+{
+	SetState(IClient::STATE_QUITING);
+}
+
+const char *CClient::ErrorString()
+{
+	return m_NetClient.ErrorString();
+}
+
+void CClient::Render()
+{
+	if(g_Config.m_GfxClear)
+		Graphics()->Clear(1,1,0);
+
+	GameClient()->OnRender();
+	DebugRender();
+}
+
+const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc)
+{
+	static char aErrorMsg[128];
+
+	SetState(IClient::STATE_LOADING);
+
+	if(!m_pMap->Load(pFilename))
+	{
+		str_format(aErrorMsg, sizeof(aErrorMsg), "map '%s' not found", pFilename);
+		return aErrorMsg;
+	}
+
+	// get the crc of the map
+	if(m_pMap->Crc() != WantedCrc)
+	{
+		m_pMap->Unload();
+		str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
+		return aErrorMsg;
+	}
+
+	// stop demo recording if we loaded a new map
+	m_DemoRecorder.Stop();
+
+	dbg_msg("client", "loaded map '%s'", pFilename);
+	m_RecivedSnapshots = 0;
+
+	str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
+	m_CurrentMapCrc = m_pMap->Crc();
+
+	return 0x0;
+}
+
+
+
+const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
+{
+	const char *pError = 0;
+	char aBuf[512];
+	dbg_msg("client", "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc);
+	SetState(IClient::STATE_LOADING);
+
+	// try the normal maps folder
+	str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
+	pError = LoadMap(pMapName, aBuf, WantedCrc);
+	if(!pError)
+		return pError;
+
+	// try the downloaded maps
+	str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
+	pError = LoadMap(pMapName, aBuf, WantedCrc);
+	return pError;
+}
+
+int CClient::PlayerScoreComp(const void *a, const void *b)
+{
+	CServerInfo::CPlayer *p0 = (CServerInfo::CPlayer *)a;
+	CServerInfo::CPlayer *p1 = (CServerInfo::CPlayer *)b;
+	if(p0->m_Score == p1->m_Score)
+		return 0;
+	if(p0->m_Score < p1->m_Score)
+		return 1;
+	return -1;
+}
+
+void CClient::ProcessPacket(CNetChunk *pPacket)
+{
+	if(pPacket->m_ClientID == -1)
+	{
+		// connectionlesss
+		if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) &&
+			mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
+		{
+			unsigned char *pVersionData = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
+			int VersionMatch = !mem_comp(pVersionData, VERSION_DATA, sizeof(VERSION_DATA));
+
+			dbg_msg("client/version", "version does %s (%d.%d.%d)",
+				VersionMatch ? "match" : "NOT match",
+				pVersionData[1], pVersionData[2], pVersionData[3]);
+
+			// assume version is out of date when version-data doesn't match
+			if (!VersionMatch)
+			{
+				str_format(m_aVersionStr, sizeof(m_aVersionStr), "%d.%d.%d", pVersionData[1], pVersionData[2], pVersionData[3]);
+			}
+		}
+
+		if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) &&
+			mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
+		{
+			int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST);
+			int Num = Size/sizeof(MASTERSRV_ADDR);
+			MASTERSRV_ADDR *pAddrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST));
+			int i;
+
+			for(i = 0; i < Num; i++)
+			{
+				NETADDR Addr;
+
+				// convert address
+				mem_zero(&Addr, sizeof(Addr));
+				Addr.type = NETTYPE_IPV4;
+				Addr.ip[0] = pAddrs[i].m_aIp[0];
+				Addr.ip[1] = pAddrs[i].m_aIp[1];
+				Addr.ip[2] = pAddrs[i].m_aIp[2];
+				Addr.ip[3] = pAddrs[i].m_aIp[3];
+				Addr.port = (pAddrs[i].m_aPort[1]<<8) | pAddrs[i].m_aPort[0];
+
+				m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0);
+			}
+		}
+
+		{
+			int PacketType = 0;
+			if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
+				PacketType = 2;
+
+			if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0)
+				PacketType = 1;
+
+			if(PacketType)
+			{
+				// we got ze info
+				CUnpacker Up;
+				CServerInfo Info = {0};
+				int Token = -1;
+
+				Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO));
+				if(PacketType >= 2)
+					Token = str_toint(Up.GetString());
+				str_copy(Info.m_aVersion, Up.GetString(), sizeof(Info.m_aVersion));
+				str_copy(Info.m_aName, Up.GetString(), sizeof(Info.m_aName));
+				str_copy(Info.m_aMap, Up.GetString(), sizeof(Info.m_aMap));
+				str_copy(Info.m_aGameType, Up.GetString(), sizeof(Info.m_aGameType));
+				Info.m_Flags = str_toint(Up.GetString());
+				Info.m_Progression = str_toint(Up.GetString());
+				Info.m_NumPlayers = str_toint(Up.GetString());
+				Info.m_MaxPlayers = str_toint(Up.GetString());
+
+				// don't add invalid info to the server browser list
+				if(Info.m_NumPlayers > MAX_CLIENTS || Info.m_MaxPlayers > MAX_CLIENTS)
+					return;
+
+				str_format(Info.m_aAddress, sizeof(Info.m_aAddress), "%d.%d.%d.%d:%d",
+					pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2],
+					pPacket->m_Address.ip[3], pPacket->m_Address.port);
+
+				for(int i = 0; i < Info.m_NumPlayers; i++)
+				{
+					str_copy(Info.m_aPlayers[i].m_aName, Up.GetString(), sizeof(Info.m_aPlayers[i].m_aName));
+					Info.m_aPlayers[i].m_Score = str_toint(Up.GetString());
+				}
+
+				if(!Up.Error())
+				{
+					// sort players
+					qsort(Info.m_aPlayers, Info.m_NumPlayers, sizeof(*Info.m_aPlayers), PlayerScoreComp);
+
+					if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0)
+					{
+						mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo));
+						m_CurrentServerInfo.m_NetAddr = m_ServerAddress;
+						m_CurrentServerInfoRequestTime = -1;
+					}
+					else
+					{
+						if(PacketType == 2)
+							m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info);
+						else
+							m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_OLD_INTERNET, -1, &Info);
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		CUnpacker Unpacker;
+		Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
+
+		// unpack msgid and system flag
+		int Msg = Unpacker.GetInt();
+		int Sys = Msg&1;
+		Msg >>= 1;
+
+		if(Unpacker.Error())
+			return;
+
+		if(Sys)
+		{
+			// system message
+			if(Msg == NETMSG_MAP_CHANGE)
+			{
+				const char *pMap = Unpacker.GetString();
+				int MapCrc = Unpacker.GetInt();
+				const char *pError = 0;
+
+				if(Unpacker.Error())
+					return;
+
+				for(int i = 0; pMap[i]; i++) // protect the player from nasty map names
+				{
+					if(pMap[i] == '/' || pMap[i] == '\\')
+						pError = "strange character in map name";
+				}
+
+				if(pError)
+					DisconnectWithReason(pError);
+				else
+				{
+					pError = LoadMapSearch(pMap, MapCrc);
+
+					if(!pError)
+					{
+						dbg_msg("client/network", "loading done");
+						SendReady();
+						GameClient()->OnConnected();
+					}
+					else
+					{
+						str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc);
+
+						dbg_msg("client/network", "starting to download map to '%s'", m_aMapdownloadFilename);
+
+						m_MapdownloadChunk = 0;
+						str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
+						m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE);
+						m_MapdownloadCrc = MapCrc;
+						m_MapdownloadTotalsize = -1;
+						m_MapdownloadAmount = 0;
+
+						CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
+						Msg.AddInt(m_MapdownloadChunk);
+						SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
+
+						if(g_Config.m_Debug)
+							dbg_msg("client/network", "requested chunk %d", m_MapdownloadChunk);
+					}
+				}
+			}
+			else if(Msg == NETMSG_MAP_DATA)
+			{
+				int Last = Unpacker.GetInt();
+				int TotalSize = Unpacker.GetInt();
+				int Size = Unpacker.GetInt();
+				const unsigned char *pData = Unpacker.GetRaw(Size);
+
+				// check fior errors
+				if(Unpacker.Error() || Size <= 0 || TotalSize <= 0 || !m_MapdownloadFile)
+					return;
+
+				io_write(m_MapdownloadFile, pData, Size);
+
+				m_MapdownloadTotalsize = TotalSize;
+				m_MapdownloadAmount += Size;
+
+				if(Last)
+				{
+					const char *pError;
+					dbg_msg("client/network", "download complete, loading map");
+
+					io_close(m_MapdownloadFile);
+					m_MapdownloadFile = 0;
+					m_MapdownloadAmount = 0;
+					m_MapdownloadTotalsize = -1;
+
+					// load map
+					pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc);
+					if(!pError)
+					{
+						dbg_msg("client/network", "loading done");
+						SendReady();
+						GameClient()->OnConnected();
+					}
+					else
+						DisconnectWithReason(pError);
+				}
+				else
+				{
+					// request new chunk
+					m_MapdownloadChunk++;
+
+					CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
+					Msg.AddInt(m_MapdownloadChunk);
+					SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
+
+					if(g_Config.m_Debug)
+						dbg_msg("client/network", "requested chunk %d", m_MapdownloadChunk);
+				}
+			}
+			else if(Msg == NETMSG_PING)
+			{
+				CMsgPacker Msg(NETMSG_PING_REPLY);
+				SendMsgEx(&Msg, 0);
+			}
+			else if(Msg == NETMSG_RCON_AUTH_STATUS)
+			{
+				int Result = Unpacker.GetInt();
+				if(Unpacker.Error() == 0)
+					m_RconAuthed = Result;
+			}
+			else if(Msg == NETMSG_RCON_LINE)
+			{
+				const char *pLine = Unpacker.GetString();
+				if(Unpacker.Error() == 0)
+				{
+					//dbg_msg("remote", "%s", line);
+					GameClient()->OnRconLine(pLine);
+				}
+			}
+			else if(Msg == NETMSG_PING_REPLY)
+				dbg_msg("client/network", "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq());
+			else if(Msg == NETMSG_INPUTTIMING)
+			{
+				int InputPredTick = Unpacker.GetInt();
+				int TimeLeft = Unpacker.GetInt();
+
+				// adjust our prediction time
+				int64 Target = 0;
+				for(int k = 0; k < 200; k++)
+				{
+					if(m_aInputs[k].m_Tick == InputPredTick)
+					{
+						Target = m_aInputs[k].m_PredictedTime + (time_get() - m_aInputs[k].m_Time);
+						Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq());
+						//st_update(&predicted_time, );
+						break;
+					}
+				}
+
+				if(Target)
+					m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1);
+			}
+			else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
+			{
+				//dbg_msg("client/network", "got snapshot");
+				int NumParts = 1;
+				int Part = 0;
+				int GameTick = Unpacker.GetInt();
+				int DeltaTick = GameTick-Unpacker.GetInt();
+				int PartSize = 0;
+				int Crc = 0;
+				int CompleteSize = 0;
+				const char *pData = 0;
+
+				// we are not allowed to process snapshot yet
+				if(State() < IClient::STATE_LOADING)
+					return;
+
+				if(Msg == NETMSG_SNAP)
+				{
+					NumParts = Unpacker.GetInt();
+					Part = Unpacker.GetInt();
+				}
+
+				if(Msg != NETMSG_SNAPEMPTY)
+				{
+					Crc = Unpacker.GetInt();
+					PartSize = Unpacker.GetInt();
+				}
+
+				pData = (const char *)Unpacker.GetRaw(PartSize);
+
+				if(Unpacker.Error())
+					return;
+
+				if(GameTick >= m_CurrentRecvTick)
+				{
+					if(GameTick != m_CurrentRecvTick)
+					{
+						m_SnapshotParts = 0;
+						m_CurrentRecvTick = GameTick;
+					}
+
+					// TODO: clean this up abit
+					mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize);
+					m_SnapshotParts |= 1<<Part;
+
+					if(m_SnapshotParts == (unsigned)((1<<NumParts)-1))
+					{
+						static CSnapshot Emptysnap;
+						CSnapshot *pDeltaShot = &Emptysnap;
+						int PurgeTick;
+						void *pDeltaData;
+						int DeltaSize;
+						unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE];
+						unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE];
+						int SnapSize;
+
+						CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize;
+
+						// reset snapshoting
+						m_SnapshotParts = 0;
+
+						// find snapshot that we should use as delta
+						Emptysnap.Clear();
+
+						// find delta
+						if(DeltaTick >= 0)
+						{
+							int DeltashotSize = m_SnapshotStorage.Get(DeltaTick, 0, &pDeltaShot, 0);
+
+							if(DeltashotSize < 0)
+							{
+								// couldn't find the delta snapshots that the server used
+								// to compress this snapshot. force the server to resync
+								if(g_Config.m_Debug)
+									dbg_msg("client", "error, couldn't find the delta snapshot");
+
+								// ack snapshot
+								// TODO: combine this with the input message
+								m_AckGameTick = -1;
+								return;
+							}
+						}
+
+						// decompress snapshot
+						pDeltaData = m_SnapshotDelta.EmptyDelta();
+						DeltaSize = sizeof(int)*3;
+
+						if(CompleteSize)
+						{
+							int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, CompleteSize, aTmpBuffer2);
+
+							if(IntSize < 0) // failure during decompression, bail
+								return;
+
+							pDeltaData = aTmpBuffer2;
+							DeltaSize = IntSize;
+						}
+
+						// unpack delta
+						PurgeTick = DeltaTick;
+						SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, (CSnapshot*)aTmpBuffer3, pDeltaData, DeltaSize);
+						if(SnapSize < 0)
+						{
+							dbg_msg("client", "delta unpack failed!");
+							return;
+						}
+
+						if(Msg != NETMSG_SNAPEMPTY && ((CSnapshot*)aTmpBuffer3)->Crc() != Crc)
+						{
+							if(g_Config.m_Debug)
+							{
+								dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
+									m_SnapCrcErrors, GameTick, Crc, ((CSnapshot*)aTmpBuffer3)->Crc(), CompleteSize, DeltaTick);
+							}
+
+							m_SnapCrcErrors++;
+							if(m_SnapCrcErrors > 10)
+							{
+								// to many errors, send reset
+								m_AckGameTick = -1;
+								SendInput();
+								m_SnapCrcErrors = 0;
+							}
+							return;
+						}
+						else
+						{
+							if(m_SnapCrcErrors)
+								m_SnapCrcErrors--;
+						}
+
+						// purge old snapshots
+						PurgeTick = DeltaTick;
+						if(m_aSnapshots[SNAP_PREV] && m_aSnapshots[SNAP_PREV]->m_Tick < PurgeTick)
+							PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
+						if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_CURRENT]->m_Tick < PurgeTick)
+							PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
+						m_SnapshotStorage.PurgeUntil(PurgeTick);
+
+						// add new
+						m_SnapshotStorage.Add(GameTick, time_get(), SnapSize, (CSnapshot*)aTmpBuffer3, 1);
+
+						// add snapshot to demo
+						if(m_DemoRecorder.IsRecording())
+						{
+
+							// write tick marker
+							/*
+							DEMOREC_TICKMARKER marker;
+							marker.tick = game_tick;
+							swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int));
+							demorec_record_write("TICK", sizeof(marker), &marker);
+							demorec_record_write("SNAP", snapsize, tmpbuffer3);
+							*/
+
+							// write snapshot
+							m_DemoRecorder.RecordSnapshot(GameTick, aTmpBuffer3, SnapSize);
+						}
+
+						// apply snapshot, cycle pointers
+						m_RecivedSnapshots++;
+
+						m_CurrentRecvTick = GameTick;
+
+						// we got two snapshots until we see us self as connected
+						if(m_RecivedSnapshots == 2)
+						{
+							// start at 200ms and work from there
+							m_PredictedTime.Init(GameTick*time_freq()/50);
+							m_PredictedTime.SetAdjustSpeed(1, 1000.0f);
+							m_GameTime.Init((GameTick-1)*time_freq()/50);
+							m_aSnapshots[SNAP_PREV] = m_SnapshotStorage.m_pFirst;
+							m_aSnapshots[SNAP_CURRENT] = m_SnapshotStorage.m_pLast;
+							m_LocalStartTime = time_get();
+							SetState(IClient::STATE_ONLINE);
+						}
+
+						// adjust game time
+						{
+							int64 Now = m_GameTime.Get(time_get());
+							int64 TickStart = GameTick*time_freq()/50;
+							int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
+							//st_update(&game_time, (game_tick-1)*time_freq()/50);
+							m_GameTime.Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
+						}
+
+						// ack snapshot
+						m_AckGameTick = GameTick;
+					}
+				}
+			}
+		}
+		else
+		{
+			// game message
+			if(m_DemoRecorder.IsRecording())
+				m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
+				// demorec_record_write("MESG", pPacket->data_size, );
+
+			GameClient()->OnMessage(Msg, &Unpacker);
+		}
+	}
+}
+
+void CClient::PumpNetwork()
+{
+	m_NetClient.Update();
+
+	if(State() != IClient::STATE_DEMOPLAYBACK)
+	{
+		// check for errors
+		if(State() != IClient::STATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE)
+		{
+			SetState(IClient::STATE_OFFLINE);
+			Disconnect();
+			dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString());
+		}
+
+		//
+		if(State() == IClient::STATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE)
+		{
+			// we switched to online
+			dbg_msg("client", "connected, sending info");
+			SetState(IClient::STATE_LOADING);
+			SendInfo();
+		}
+	}
+
+	// process packets
+	CNetChunk Packet;
+	while(m_NetClient.Recv(&Packet))
+		ProcessPacket(&Packet);
+}
+
+void CClient::OnDemoPlayerSnapshot(void *pData, int Size)
+{
+	// update ticks, they could have changed
+	const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
+	CSnapshotStorage::CHolder *pTemp;
+	m_CurGameTick = pInfo->m_Info.m_CurrentTick;
+	m_PrevGameTick = pInfo->m_PreviousTick;
+
+	// handle snapshots
+	pTemp = m_aSnapshots[SNAP_PREV];
+	m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
+	m_aSnapshots[SNAP_CURRENT] = pTemp;
+
+	mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pSnap, pData, Size);
+	mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size);
+
+	GameClient()->OnNewSnapshot();
+}
+
+void CClient::OnDemoPlayerMessage(void *pData, int Size)
+{
+	CUnpacker Unpacker;
+	Unpacker.Reset(pData, Size);
+
+	// unpack msgid and system flag
+	int Msg = Unpacker.GetInt();
+	int Sys = Msg&1;
+	Msg >>= 1;
+
+	if(Unpacker.Error())
+		return;
+
+	if(!Sys)
+		GameClient()->OnMessage(Msg, &Unpacker);
+}
+/*
+const IDemoPlayer::CInfo *client_demoplayer_getinfo()
+{
+	static DEMOPLAYBACK_INFO ret;
+	const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info();
+	ret.first_tick = info->first_tick;
+	ret.last_tick = info->last_tick;
+	ret.current_tick = info->current_tick;
+	ret.paused = info->paused;
+	ret.speed = info->speed;
+	return &ret;
+}*/
+
+/*
+void DemoPlayer()->SetPos(float percent)
+{
+	demorec_playback_set(percent);
+}
+
+void DemoPlayer()->SetSpeed(float speed)
+{
+	demorec_playback_setspeed(speed);
+}
+
+void DemoPlayer()->SetPause(int paused)
+{
+	if(paused)
+		demorec_playback_pause();
+	else
+		demorec_playback_unpause();
+}*/
+
+void CClient::Update()
+{
+	if(State() == IClient::STATE_DEMOPLAYBACK)
+	{
+		m_DemoPlayer.Update();
+		if(m_DemoPlayer.IsPlaying())
+		{
+			// update timers
+			const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
+			m_CurGameTick = pInfo->m_Info.m_CurrentTick;
+			m_PrevGameTick = pInfo->m_PreviousTick;
+			m_GameIntraTick = pInfo->m_IntraTick;
+			m_GameTickTime = pInfo->m_TickTime;
+		}
+		else
+		{
+			// disconnect on error
+			Disconnect();
+		}
+	}
+	else if(State() != IClient::STATE_OFFLINE && m_RecivedSnapshots >= 3)
+	{
+		// switch snapshot
+		int Repredict = 0;
+		int64 Freq = time_freq();
+		int64 Now = m_GameTime.Get(time_get());
+		int64 PredNow = m_PredictedTime.Get(time_get());
+
+		while(1)
+		{
+			CSnapshotStorage::CHolder *pCur = m_aSnapshots[SNAP_CURRENT];
+			int64 TickStart = (pCur->m_Tick)*time_freq()/50;
+
+			if(TickStart < Now)
+			{
+				CSnapshotStorage::CHolder *pNext = m_aSnapshots[SNAP_CURRENT]->m_pNext;
+				if(pNext)
+				{
+					m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
+					m_aSnapshots[SNAP_CURRENT] = pNext;
+
+					// set ticks
+					m_CurGameTick = m_aSnapshots[SNAP_CURRENT]->m_Tick;
+					m_PrevGameTick = m_aSnapshots[SNAP_PREV]->m_Tick;
+
+					if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
+					{
+						GameClient()->OnNewSnapshot();
+						Repredict = 1;
+					}
+				}
+				else
+					break;
+			}
+			else
+				break;
+		}
+
+		if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
+		{
+			int64 CurtickStart = (m_aSnapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50;
+			int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
+			//tg_add(&predicted_time_graph, pred_now, 0);
+			int PrevPredTick = (int)(PredNow*50/time_freq());
+			int NewPredTick = PrevPredTick+1;
+			static float LastPredintra = 0;
+
+			m_GameIntraTick = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
+			m_GameTickTime = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
+
+			CurtickStart = NewPredTick*time_freq()/50;
+			PrevtickStart = PrevPredTick*time_freq()/50;
+			m_PredIntraTick = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
+
+			if(NewPredTick < m_aSnapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
+			{
+				dbg_msg("client", "prediction time reset!");
+				m_PredictedTime.Init(m_aSnapshots[SNAP_CURRENT]->m_Tick*time_freq()/50);
+			}
+
+			if(NewPredTick > m_PredTick)
+			{
+				LastPredintra = m_PredIntraTick;
+				m_PredTick = NewPredTick;
+				Repredict = 1;
+
+				// send input
+				SendInput();
+			}
+
+			LastPredintra = m_PredIntraTick;
+		}
+
+		// only do sane predictions
+		if(Repredict)
+		{
+			if(m_PredTick > m_CurGameTick && m_PredTick < m_CurGameTick+50)
+				GameClient()->OnPredict();
+		}
+
+		// fetch server info if we don't have it
+		if(State() >= IClient::STATE_LOADING &&
+			m_CurrentServerInfoRequestTime >= 0 &&
+			time_get() > m_CurrentServerInfoRequestTime)
+		{
+			m_ServerBrowser.Request(m_ServerAddress);
+			m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
+		}
+	}
+
+	// STRESS TEST: join the server again
+	if(g_Config.m_DbgStress)
+	{
+		static int64 ActionTaken = 0;
+		int64 Now = time_get();
+		if(State() == IClient::STATE_OFFLINE)
+		{
+			if(Now > ActionTaken+time_freq()*2)
+			{
+				dbg_msg("stress", "reconnecting!");
+				Connect(g_Config.m_DbgStressServer);
+				ActionTaken = Now;
+			}
+		}
+		else
+		{
+			/*if(now > action_taken+time_freq()*(10+config.dbg_stress))
+			{
+				dbg_msg("stress", "disconnecting!");
+				Disconnect();
+				action_taken = now;
+			}*/
+		}
+	}
+
+	// pump the network
+	PumpNetwork();
+
+	// update the maser server registry
+	MasterServer()->Update();
+
+	// update the server browser
+	m_ServerBrowser.Update();
+}
+
+const char *CClient::UserDirectory()
+{
+	static char saPath[1024] = {0};
+	fs_storage_path("Teeworlds", saPath, sizeof(saPath));
+	return saPath;
+}
+
+void CClient::VersionUpdate()
+{
+	if(m_VersionInfo.m_State == 0)
+	{
+		m_Engine.HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer);
+		m_VersionInfo.m_State++;
+	}
+	else if(m_VersionInfo.m_State == 1)
+	{
+		if(m_VersionInfo.m_VersionServeraddr.m_Job.Status() == CJob::STATE_DONE)
+		{
+			CNetChunk Packet;
+
+			mem_zero(&Packet, sizeof(Packet));
+
+			m_VersionInfo.m_VersionServeraddr.m_Addr.port = VERSIONSRV_PORT;
+
+			Packet.m_ClientID = -1;
+			Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
+			Packet.m_pData = VERSIONSRV_GETVERSION;
+			Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION);
+			Packet.m_Flags = NETSENDFLAG_CONNLESS;
+
+			m_NetClient.Send(&Packet);
+			m_VersionInfo.m_State++;
+		}
+	}
+}
+
+void CClient::InitEngine(const char *pAppname)
+{
+	m_Engine.Init(pAppname);
+}
+
+void CClient::RegisterInterfaces()
+{
+	Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer));
+	Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser));
+}
+
+void CClient::InitInterfaces()
+{
+	// fetch interfaces
+	m_pEditor = Kernel()->RequestInterface<IEditor>();
+	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
+	m_pSound = Kernel()->RequestInterface<IEngineSound>();
+	m_pGameClient = Kernel()->RequestInterface<IGameClient>();
+	m_pInput = Kernel()->RequestInterface<IEngineInput>();
+	m_pMap = Kernel()->RequestInterface<IEngineMap>();
+	m_pMasterServer = Kernel()->RequestInterface<IEngineMasterServer>();
+	m_pStorage = Kernel()->RequestInterface<IStorage>();
+
+	//
+	m_ServerBrowser.SetBaseInfo(&m_NetClient, m_pGameClient->NetVersion());
+}
+
+void CClient::Run()
+{
+	int64 ReportTime = time_get();
+	int64 ReportInterval = time_freq()*1;
+
+	m_LocalStartTime = time_get();
+	m_SnapshotParts = 0;
+
+	// init graphics
+	if(m_pGraphics->Init() != 0)
+		return;
+
+	// init font rendering
+	Kernel()->RequestInterface<IEngineTextRender>()->Init();
+
+	// init the input
+	Input()->Init();
+
+	// start refreshing addresses while we load
+	MasterServer()->RefreshAddresses();
+
+	// init the editor
+	m_pEditor->Init();
+
+	// init sound, allowed to fail
+	Sound()->Init();
+
+	// load data
+	if(!LoadData())
+		return;
+
+	GameClient()->OnInit();
+	dbg_msg("client", "version %s", GameClient()->NetVersion());
+
+	// open socket
+	{
+		NETADDR BindAddr;
+		mem_zero(&BindAddr, sizeof(BindAddr));
+		m_NetClient.Open(BindAddr, 0);
+	}
+
+	// connect to the server if wanted
+	/*
+	if(config.cl_connect[0] != 0)
+		Connect(config.cl_connect);
+	config.cl_connect[0] = 0;
+	*/
+
+	//
+	m_FpsGraph.Init(0.0f, 200.0f);
+
+	// never start with the editor
+	g_Config.m_ClEditor = 0;
+
+	Input()->MouseModeRelative();
+
+	while (1)
+	{
+		int64 FrameStartTime = time_get();
+		m_Frames++;
+
+		//
+		VersionUpdate();
+
+		// handle pending connects
+		if(m_aCmdConnect[0])
+		{
+			str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress));
+			Connect(m_aCmdConnect);
+			m_aCmdConnect[0] = 0;
+		}
+
+		// update input
+		Input()->Update();
+
+		// update sound
+		Sound()->Update();
+
+		// release focus
+		if(!m_pGraphics->WindowActive())
+		{
+			if(m_WindowMustRefocus == 0)
+				Input()->MouseModeAbsolute();
+			m_WindowMustRefocus = 1;
+		}
+		else if (g_Config.m_DbgFocus && Input()->KeyPressed(KEY_ESCAPE))
+		{
+			Input()->MouseModeAbsolute();
+			m_WindowMustRefocus = 1;
+		}
+
+		// refocus
+		if(m_WindowMustRefocus && m_pGraphics->WindowActive())
+		{
+			if(m_WindowMustRefocus < 3)
+			{
+				Input()->MouseModeAbsolute();
+				m_WindowMustRefocus++;
+			}
+
+			if(Input()->KeyPressed(KEY_MOUSE_1))
+			{
+				Input()->MouseModeRelative();
+				m_WindowMustRefocus = 0;
+			}
+		}
+
+		// panic quit button
+		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyPressed('q'))
+			break;
+
+		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('d'))
+			g_Config.m_Debug ^= 1;
+
+		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('g'))
+			g_Config.m_DbgGraphs ^= 1;
+
+		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('e'))
+		{
+			g_Config.m_ClEditor = g_Config.m_ClEditor^1;
+			Input()->MouseModeRelative();
+		}
+
+		/*
+		if(!gfx_window_open())
+			break;
+		*/
+
+		// render
+		if(g_Config.m_ClEditor)
+		{
+			Update();
+			m_pEditor->UpdateAndRender();
+			m_pGraphics->Swap();
+		}
+		else
+		{
+			Update();
+
+			if(g_Config.m_DbgStress)
+			{
+				if((m_Frames%10) == 0)
+				{
+					Render();
+					m_pGraphics->Swap();
+				}
+			}
+			else
+			{
+				Render();
+				m_pGraphics->Swap();
+			}
+		}
+
+		// check conditions
+		if(State() == IClient::STATE_QUITING)
+			break;
+
+		// beNice
+		if(g_Config.m_DbgStress)
+			thread_sleep(5);
+		else if(g_Config.m_ClCpuThrottle || !m_pGraphics->WindowActive())
+			thread_sleep(1);
+
+		if(g_Config.m_DbgHitch)
+		{
+			thread_sleep(g_Config.m_DbgHitch);
+			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,
+					m_NetClient.State());
+			}
+			m_FrameTimeLow = 1;
+			m_FrameTimeHigh = 0;
+			m_Frames = 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;
+
+		m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
+
+		m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1);
+	}
+
+	GameClient()->OnShutdown();
+	Disconnect();
+
+	m_pGraphics->Shutdown();
+	m_pSound->Shutdown();
+}
+
+
+void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect));
+}
+
+void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->Disconnect();
+}
+
+void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->Quit();
+}
+
+void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->Graphics()->Minimize();
+}
+
+void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+
+	CMsgPacker Msg(NETMSG_PING);
+	pSelf->SendMsgEx(&Msg, 0);
+	pSelf->m_PingStartTime = time_get();
+}
+
+void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->Graphics()->TakeScreenshot();
+}
+
+void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->Rcon(pResult->GetString(0));
+}
+
+void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->RconAuth("", pResult->GetString(0));
+}
+
+void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	NETADDR Addr;
+	if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
+		pSelf->m_ServerBrowser.AddFavorite(Addr);
+}
+
+const char *CClient::DemoPlayer_Play(const char *pFilename)
+{
+	int Crc;
+	const char *pError;
+	Disconnect();
+	m_NetClient.ResetErrorString();
+
+	// try to start playback
+	m_DemoPlayer.SetListner(this);
+
+	if(m_DemoPlayer.Load(Storage(), pFilename))
+		return "error loading demo";
+
+	// load map
+	Crc = (m_DemoPlayer.Info()->m_Header.m_aCrc[0]<<24)|
+		(m_DemoPlayer.Info()->m_Header.m_aCrc[1]<<16)|
+		(m_DemoPlayer.Info()->m_Header.m_aCrc[2]<<8)|
+		(m_DemoPlayer.Info()->m_Header.m_aCrc[3]);
+	pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMap, Crc);
+	if(pError)
+	{
+		DisconnectWithReason(pError);
+		return pError;
+	}
+
+	GameClient()->OnConnected();
+
+	// setup buffers
+	mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData));
+
+	m_aSnapshots[SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT];
+	m_aSnapshots[SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV];
+
+	m_aSnapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0];
+	m_aSnapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1];
+	m_aSnapshots[SNAP_CURRENT]->m_SnapSize = 0;
+	m_aSnapshots[SNAP_CURRENT]->m_Tick = -1;
+
+	m_aSnapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0];
+	m_aSnapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1];
+	m_aSnapshots[SNAP_PREV]->m_SnapSize = 0;
+	m_aSnapshots[SNAP_PREV]->m_Tick = -1;
+
+	// enter demo playback state
+	SetState(IClient::STATE_DEMOPLAYBACK);
+
+	m_DemoPlayer.Play();
+	GameClient()->OnEnterGame();
+
+	return 0;
+}
+
+void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->DemoPlayer_Play(pResult->GetString(0));
+}
+
+void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	if(pSelf->State() != IClient::STATE_ONLINE)
+		dbg_msg("demorec/record", "client is not online");
+	else
+	{
+		char aFilename[512];
+		str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pResult->GetString(0));
+		pSelf->m_DemoRecorder.Start(pSelf->Storage(), aFilename, pSelf->GameClient()->NetVersion(), pSelf->m_aCurrentMap, pSelf->m_CurrentMapCrc, "client");
+	}
+}
+
+void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->m_DemoRecorder.Stop();
+}
+
+void CClient::Con_ServerDummy(IConsole::IResult *pResult, void *pUserData)
+{
+	dbg_msg("client", "this command is not available on the client");
+}
+
+void CClient::RegisterCommands()
+{
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
+	// register server dummy commands for tab completion
+	m_pConsole->Register("kick", "i", CFGFLAG_SERVER, Con_ServerDummy, this, "Kick player with specified id");
+	m_pConsole->Register("ban", "s?i", CFGFLAG_SERVER, Con_ServerDummy, this, "Ban player with ip/id for x minutes");
+	m_pConsole->Register("unban", "s", CFGFLAG_SERVER, Con_ServerDummy, this, "Unban ip");
+	m_pConsole->Register("bans", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Show banlist");
+	m_pConsole->Register("status", "", CFGFLAG_SERVER, Con_ServerDummy, this, "List players");
+	m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Shut down");
+	m_pConsole->Register("record", "s", CFGFLAG_SERVER, Con_ServerDummy, this, "Record to a file");
+	m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, Con_ServerDummy, this, "Stop recording");
+
+	m_pConsole->Register("quit", "", CFGFLAG_CLIENT, Con_Quit, this, "Quit Teeworlds");
+	m_pConsole->Register("exit", "", CFGFLAG_CLIENT, Con_Quit, this, "Quit Teeworlds");
+	m_pConsole->Register("minimize", "", CFGFLAG_CLIENT, Con_Minimize, this, "Minimize Teeworlds");
+	m_pConsole->Register("connect", "s", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip");
+	m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server");
+	m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server");
+	m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
+	m_pConsole->Register("rcon", "r", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
+	m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
+	m_pConsole->Register("play", "r", CFGFLAG_CLIENT, Con_Play, this, "Play the file specified");
+	m_pConsole->Register("record", "s", CFGFLAG_CLIENT, Con_Record, this, "Record to the file");
+	m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording");
+
+	m_pConsole->Register("add_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite");
+}
+
+static CClient m_Client;
+
+/*
+	Server Time
+	Client Mirror Time
+	Client Predicted Time
+
+	Snapshot Latency
+		Downstream latency
+
+	Prediction Latency
+		Upstream latency
+*/
+
+#if defined(CONF_PLATFORM_MACOSX)
+int SDL_main(int argc, const char **argv) // ignore_convention
+#else
+int main(int argc, const char **argv) // ignore_convention
+#endif
+{
+	// init the engine
+	dbg_msg("client", "starting...");
+	m_Client.InitEngine("Teeworlds");
+
+	IKernel *pKernel = IKernel::Create();
+	pKernel->RegisterInterface(&m_Client);
+	m_Client.RegisterInterfaces();
+
+	// create the components
+	IConsole *pConsole = CreateConsole();
+	IStorage *pStorage = CreateStorage("Teeworlds", argv[0]); // ignore_convention
+	IConfig *pConfig = CreateConfig();
+	IEngineGraphics *pEngineGraphics = CreateEngineGraphics();
+	IEngineSound *pEngineSound = CreateEngineSound();
+	IEngineInput *pEngineInput = CreateEngineInput();
+	IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
+	IEngineMap *pEngineMap = CreateEngineMap();
+	IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer();
+
+	{
+		bool RegisterFail = false;
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IConsole*>(pConsole));
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IConfig*>(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));
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineInput*>(pEngineInput)); // register as both
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IInput*>(pEngineInput));
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineTextRender*>(pEngineTextRender)); // register as both
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ITextRender*>(pEngineTextRender));
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap));
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer));
+
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor());
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient());
+		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
+
+		if(RegisterFail)
+			return -1;
+	}
+
+	pConfig->Init();
+	pEngineMasterServer->Init(m_Client.Engine());
+	pEngineMasterServer->Load();
+
+	// register all console commands
+	m_Client.RegisterCommands();
+
+	pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
+
+	// init client's interfaces
+	m_Client.InitInterfaces();
+
+	// execute autoexec file
+	pConsole->ExecuteFile("autoexec.cfg");
+
+	// parse the command line arguments
+	if(argc > 1) // ignore_convention
+		pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention
+
+	// execute config file
+	pConsole->ExecuteFile("settings.cfg");
+
+	// run the client
+	m_Client.Run();
+
+	// write down the config and quit
+	pConfig->Save();
+
+	return 0;
+}
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index 64ef6d9b..c7174f85 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -1,17 +1,291 @@
+#ifndef ENGINE_CLIENT_CLIENT_H
+#define ENGINE_CLIENT_CLIENT_H
 
 
-class IEngine
+#include <engine/console.h>
+#include <engine/editor.h>
+#include <engine/graphics.h>
+#include <engine/textrender.h>
+#include <engine/client.h>
+#include <engine/config.h>
+#include <engine/serverbrowser.h>
+#include <engine/sound.h>
+#include <engine/input.h>
+#include <engine/keys.h>
+#include <engine/map.h>
+#include <engine/masterserver.h>
+#include <engine/storage.h>
+
+#include <engine/shared/engine.h>
+#include <engine/shared/protocol.h>
+#include <engine/shared/demorec.h>
+#include <engine/shared/network.h>
+
+#include "srvbrowse.h"
+
+class CGraph
 {
 public:
-	virtual ~IEngine() {}
-	virtual class IGraphics *Graphics() = 0;
+	enum
+	{
+		// restrictions: Must be power of two
+		MAX_VALUES=128,
+	};
+
+	float m_Min, m_Max;
+	float m_aValues[MAX_VALUES];
+	float m_aColors[MAX_VALUES][3];
+	int m_Index;
+
+	void Init(float Min, float Max);
+
+	void ScaleMax();
+	void ScaleMin();
+
+	void Add(float v, float r, float g, float b);
+	void Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription);
 };
 
 
-class IGameClient
+class CSmoothTime
 {
+	int64 m_Snap;
+	int64 m_Current;
+	int64 m_Target;
+
+	int64 m_RLast;
+	int64 m_TLast;
+	CGraph m_Graph;
+
+	int m_SpikeCounter;
+
+	float m_aAdjustSpeed[2]; // 0 = down, 1 = up
 public:
-	virtual ~IGameClient() {}
+	void Init(int64 Target);
+	void SetAdjustSpeed(int Direction, float Value);
+
+	int64 Get(int64 Now);
+
+	void UpdateInt(int64 Target);
+	void Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection);
 };
 
-extern IGameClient *CreateGameClient(IEngine *pEngine);
+
+class CClient : public IClient, public CDemoPlayer::IListner
+{
+	// needed interfaces
+	IEditor *m_pEditor;
+	IEngineInput *m_pInput;
+	IEngineGraphics *m_pGraphics;
+	IEngineSound *m_pSound;
+	IGameClient *m_pGameClient;
+	IEngineMap *m_pMap;
+	IConsole *m_pConsole;
+	IStorage *m_pStorage;
+	IEngineMasterServer *m_pMasterServer;
+
+	enum
+	{
+		NUM_SNAPSHOT_TYPES=2,
+		PREDICTION_MARGIN=1000/50/2, // magic network prediction value
+	};
+
+	CNetClient m_NetClient;
+	CDemoPlayer m_DemoPlayer;
+	CDemoRecorder m_DemoRecorder;
+	CEngine m_Engine;
+	CServerBrowser m_ServerBrowser;
+
+	char m_aServerAddressStr[256];
+
+	unsigned m_SnapshotParts;
+	int64 m_LocalStartTime;
+
+	int m_DebugFont;
+	float m_FrameTimeLow;
+	float m_FrameTimeHigh;
+	int m_Frames;
+	NETADDR m_ServerAddress;
+	int m_WindowMustRefocus;
+	int m_SnapCrcErrors;
+
+	int m_AckGameTick;
+	int m_CurrentRecvTick;
+	int m_RconAuthed;
+
+	// version-checking
+	char m_aVersionStr[10];
+
+	// pinging
+	int64 m_PingStartTime;
+
+	//
+	char m_aCurrentMap[256];
+	int m_CurrentMapCrc;
+
+	//
+	char m_aCmdConnect[256];
+
+	// map download
+	char m_aMapdownloadFilename[256];
+	char m_aMapdownloadName[256];
+	IOHANDLE m_MapdownloadFile;
+	int m_MapdownloadChunk;
+	int m_MapdownloadCrc;
+	int m_MapdownloadAmount;
+	int m_MapdownloadTotalsize;
+
+	// time
+	CSmoothTime m_GameTime;
+	CSmoothTime m_PredictedTime;
+
+	// input
+	struct // TODO: handle input better
+	{
+		int m_aData[MAX_INPUT_SIZE]; // the input data
+		int m_Tick; // the tick that the input is for
+		int64 m_PredictedTime; // prediction latency when we sent this input
+		int64 m_Time;
+	} m_aInputs[200];
+
+	int m_CurrentInput;
+
+	// graphs
+	CGraph m_InputtimeMarginGraph;
+	CGraph m_GametimeMarginGraph;
+	CGraph m_FpsGraph;
+
+	// the game snapshots are modifiable by the game
+	CSnapshotStorage m_SnapshotStorage;
+	CSnapshotStorage::CHolder *m_aSnapshots[NUM_SNAPSHOT_TYPES];
+
+	int m_RecivedSnapshots;
+	char m_aSnapshotIncommingData[CSnapshot::MAX_SIZE];
+
+	CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES];
+	char *m_aDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE];
+
+	CSnapshotDelta m_SnapshotDelta;
+
+	//
+	CServerInfo m_CurrentServerInfo;
+	int64 m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info
+
+	// version info
+	struct
+	{
+		int m_State;
+		CHostLookup m_VersionServeraddr;
+	} m_VersionInfo;
+public:
+	IEngineGraphics *Graphics() { return m_pGraphics; }
+	IEngineInput *Input() { return m_pInput; }
+	IEngineSound *Sound() { return m_pSound; }
+	IGameClient *GameClient() { return m_pGameClient; }
+	IEngineMasterServer *MasterServer() { return m_pMasterServer; }
+	IStorage *Storage() { return m_pStorage; }
+
+	CClient();
+
+	// ----- send functions -----
+	virtual int SendMsg(CMsgPacker *pMsg, int Flags);
+
+	int SendMsgEx(CMsgPacker *pMsg, int Flags, bool System=true);
+	void SendInfo();
+	void SendEnterGame();
+	void SendReady();
+
+	virtual bool RconAuthed();
+	void RconAuth(const char *pName, const char *pPassword);
+	virtual void Rcon(const char *pCmd);
+
+	virtual bool ConnectionProblems();
+
+	void DirectInput(int *pInput, int Size);
+	void SendInput();
+
+	// TODO: OPT: do this alot smarter!
+	virtual int *GetInput(int Tick);
+
+	const char *LatestVersion();
+	void VersionUpdate();
+
+	// ------ state handling -----
+	void SetState(int s);
+
+	// called when the map is loaded and we should init for a new round
+	void OnEnterGame();
+	virtual void EnterGame();
+
+	virtual void Connect(const char *pAddress);
+	void DisconnectWithReason(const char *pReason);
+	virtual void Disconnect();
+
+
+	virtual void GetServerInfo(CServerInfo *pServerInfo);
+	void ServerInfoRequest();
+
+	int LoadData();
+
+	// ---
+
+	void *SnapGetItem(int SnapId, int Index, CSnapItem *pItem);
+	void SnapInvalidateItem(int SnapId, int Index);
+	void *SnapFindItem(int SnapId, int Type, int Id);
+	int SnapNumItems(int SnapId);
+	void SnapSetStaticsize(int ItemType, int Size);
+
+	void Render();
+	void DebugRender();
+
+	virtual void Quit();
+
+	virtual const char *ErrorString();
+
+	const char *LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc);
+	const char *LoadMapSearch(const char *pMapName, int WantedCrc);
+
+	static int PlayerScoreComp(const void *a, const void *b);
+
+	void ProcessPacket(CNetChunk *pPacket);
+
+	virtual int MapDownloadAmount() { return m_MapdownloadAmount; }
+	virtual int MapDownloadTotalsize() { return m_MapdownloadTotalsize; }
+
+	void PumpNetwork();
+
+	virtual void OnDemoPlayerSnapshot(void *pData, int Size);
+	virtual void OnDemoPlayerMessage(void *pData, int Size);
+
+	void Update();
+
+	virtual const char *UserDirectory();
+
+	void InitEngine(const char *pAppname);
+	void RegisterInterfaces();
+	void InitInterfaces();
+
+	void Run();
+
+
+	static void Con_Connect(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Disconnect(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Quit(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Minimize(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Ping(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Rcon(IConsole::IResult *pResult, void *pUserData);
+	static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData);
+	static void Con_AddFavorite(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Play(IConsole::IResult *pResult, void *pUserData);
+	static void Con_Record(IConsole::IResult *pResult, void *pUserData);
+	static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData);
+	static void Con_ServerDummy(IConsole::IResult *pResult, void *pUserData);
+
+	void RegisterCommands();
+
+	const char *DemoPlayer_Play(const char *pFilename);
+
+	virtual class CEngine *Engine() { return &m_Engine; }
+};
+#endif
diff --git a/src/engine/client/ec_client.cpp b/src/engine/client/ec_client.cpp
deleted file mode 100644
index 7ba0a2bb..00000000
--- a/src/engine/client/ec_client.cpp
+++ /dev/null
@@ -1,2087 +0,0 @@
-/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
-
-#include <string.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-
-#include <base/system.h>
-#include <engine/e_engine.h>
-#include <engine/e_client_interface.h>
-
-#include <engine/e_protocol.h>
-#include <engine/e_snapshot.h>
-#include <engine/e_compression.h>
-#include <engine/e_network.h>
-#include <engine/e_config.h>
-#include <engine/e_packer.h>
-#include <engine/e_memheap.h>
-#include <engine/e_datafile.h>
-#include <engine/e_console.h>
-#include <engine/e_ringbuffer.h>
-
-#include <engine/e_huffman.h>
-
-#include <engine/e_demorec.h>
-
-#include <mastersrv/mastersrv.h>
-#include <versionsrv/versionsrv.h>
-
-#include "editor.h"
-#include "graphics.h"
-#include "client.h"
-
-static IEditor *m_pEditor = 0;
-static IEngineGraphics *m_pGraphics = 0;
-IEngineGraphics *Graphics() { return m_pGraphics; }
-
-static IGameClient *m_pGameClient = 0;
-
-
-class CClient : public IEngine
-{
-public:
-	virtual class IGraphics *Graphics()
-	{
-		return m_pGraphics;
-	}
-};
-
-static CClient m_Client;
-
-const int prediction_margin = 1000/50/2; /* magic network prediction value */
-
-/*
-	Server Time
-	Client Mirror Time
-	Client Predicted Time
-	
-	Snapshot Latency
-		Downstream latency
-	
-	Prediction Latency
-		Upstream latency
-*/
-
-/* network client, must be accessible from other parts like the server browser */
-CNetClient m_NetClient;
-
-/* TODO: ugly, fix me */
-extern void client_serverbrowse_set(NETADDR *addr, int request, int token, SERVER_INFO *info);
-extern void client_serverbrowse_save();
-
-static unsigned snapshot_parts;
-static int64 local_start_time;
-
-static int debug_font;
-static float frametime = 0.0001f;
-static float frametime_low = 1.0f;
-static float frametime_high = 0.0f;
-static int frames = 0;
-static NETADDR server_address;
-static int window_must_refocus = 0;
-static int snapcrcerrors = 0;
-
-static int ack_game_tick = -1;
-static int current_recv_tick = 0;
-static int rcon_authed = 0;
-
-/* version-checking */
-static char versionstr[10] = "0";
-
-/* pinging */
-static int64 ping_start_time = 0;
-
-/* */
-static char current_map[256] = {0};
-static int current_map_crc = 0;
-
-/* */
-static char cmd_connect[256] = {0};
-
-/* map download */
-static char mapdownload_filename[256] = {0};
-static char mapdownload_name[256] = {0};
-static IOHANDLE mapdownload_file = 0;
-static int mapdownload_chunk = 0;
-static int mapdownload_crc = 0;
-static int mapdownload_amount = -1;
-static int mapdownload_totalsize = -1;
-
-/* */
-static SERVER_INFO current_server_info = {0};
-static int64 current_server_info_requesttime = -1; /* >= 0 should request, == -1 got info */
-
-/* current time */
-static int current_tick = 0;
-static float intratick = 0;
-static float ticktime = 0;
-static int prev_tick = 0;
-
-/* */
-/*static int predictiontime_pingspikecounter = 0;
-static int gametime_pingspikecounter = 0;*/
-
-/* predicted time */
-static int current_predtick = 0;
-static float predintratick = 0;
-static int last_input_timeleft = 0;
-
-static struct /* TODO: handle input better */
-{
-	int data[MAX_INPUT_SIZE]; /* the input data */
-	int tick; /* the tick that the input is for */
-	int64 predicted_time; /* prediction latency when we sent this input */
-	int64 time;
-} inputs[200];
-static int current_input = 0;
-
-enum
-{
-	GRAPH_MAX=128
-};
-
-typedef struct
-{
-	float min, max;
-	float values[GRAPH_MAX];
-	float colors[GRAPH_MAX][3];
-	int index;
-} GRAPH;
-
-static void graph_init(GRAPH *g, float min, float max)
-{
-	g->min = min;
-	g->max = max;
-	g->index = 0;
-}
-
-static void graph_scale_max(GRAPH *g)
-{
-	int i = 0;
-	g->max = 0;
-	for(i = 0; i < GRAPH_MAX; i++)
-	{
-		if(g->values[i] > g->max)
-			g->max = g->values[i];
-	}
-}
-
-static void graph_scale_min(GRAPH *g)
-{
-	int i = 0;
-	g->min = g->max;
-	for(i = 0; i < GRAPH_MAX; i++)
-	{
-		if(g->values[i] < g->min)
-			g->min = g->values[i];
-	}
-}
-
-static void graph_add(GRAPH *graph, float v, float r, float g, float b)
-{
-	graph->index = (graph->index+1)&(GRAPH_MAX-1);
-	graph->values[graph->index] = v;
-	graph->colors[graph->index][0] = r;
-	graph->colors[graph->index][1] = g;
-	graph->colors[graph->index][2] = b;
-}
-
-static void graph_render(GRAPH *g, float x, float y, float w, float h, const char *description)
-{
-	char buf[32];
-	int i;
-
-	//m_pGraphics->BlendNormal();
-
-	
-	Graphics()->TextureSet(-1);
-	
-	m_pGraphics->QuadsBegin();
-	Graphics()->SetColor(0, 0, 0, 0.75f);
-	Graphics()->QuadsDrawTL(x, y, w, h);
-	m_pGraphics->QuadsEnd();
-		
-	Graphics()->LinesBegin();
-	Graphics()->SetColor(0.95f, 0.95f, 0.95f, 1.00f);
-	Graphics()->LinesDraw(x, y+h/2, x+w, y+h/2);
-	Graphics()->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
-	Graphics()->LinesDraw(x, y+(h*3)/4, x+w, y+(h*3)/4);
-	Graphics()->LinesDraw(x, y+h/4, x+w, y+h/4);
-	for(i = 1; i < GRAPH_MAX; i++)
-	{
-		float a0 = (i-1)/(float)GRAPH_MAX;
-		float a1 = i/(float)GRAPH_MAX;
-		int i0 = (g->index+i-1)&(GRAPH_MAX-1);
-		int i1 = (g->index+i)&(GRAPH_MAX-1);
-		
-		float v0 = (g->values[i0]-g->min) / (g->max-g->min);
-		float v1 = (g->values[i1]-g->min) / (g->max-g->min);
-		
-		Graphics()->SetColorVertex(0, g->colors[i0][0], g->colors[i0][1], g->colors[i0][2], 0.75f);
-		Graphics()->SetColorVertex(1, g->colors[i1][0], g->colors[i1][1], g->colors[i1][2], 0.75f);
-		Graphics()->LinesDraw(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
-
-	}
-	Graphics()->LinesEnd();
-	
-
-	Graphics()->TextureSet(debug_font);	
-	Graphics()->QuadsText(x+2, y+h-16, 16, 1,1,1,1, description);
-
-	str_format(buf, sizeof(buf), "%.2f", g->max);
-	Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+2, 16, 1,1,1,1, buf);
-	
-	str_format(buf, sizeof(buf), "%.2f", g->min);
-	Graphics()->QuadsText(x+w-8*strlen(buf)-8, y+h-16, 16, 1,1,1,1, buf);
-	
-}
-
-typedef struct
-{
-	int64 snap;
-	int64 current;
-	int64 target;
-	
-	int64 rlast;
-	int64 tlast;
-	GRAPH graph;
-	
-	int spikecounter;
-	
-	float adjustspeed[2]; /* 0 = down, 1 = up */
-} SMOOTHTIME;
-
-static void st_init(SMOOTHTIME *st, int64 target)
-{
-	st->snap = time_get();
-	st->current = target;
-	st->target = target;
-	st->adjustspeed[0] = 0.3f;
-	st->adjustspeed[1] = 0.3f;
-	graph_init(&st->graph, 0.0f, 0.5f);
-}
-
-static int64 st_get(SMOOTHTIME *st, int64 now)
-{
-	float adjust_speed, a;
-	int64 c = st->current + (now - st->snap);
-	int64 t = st->target + (now - st->snap);
-	int64 r;
-	
-	/* it's faster to adjust upward instead of downward */
-	/* we might need to adjust these abit */
-
-	adjust_speed = st->adjustspeed[0];
-	if(t > c)
-		adjust_speed = st->adjustspeed[1];
-	
-	a = ((now-st->snap)/(float)time_freq()) * adjust_speed;
-	if(a > 1.0f)
-		a = 1.0f;
-		
-	r = c + (int64)((t-c)*a);
-	
-	graph_add(&st->graph, a+0.5f,1,1,1);
-	
-	return r;
-}
-
-static void st_update_int(SMOOTHTIME *st, int64 target)
-{
-	int64 now = time_get();
-	st->current = st_get(st, now);
-	st->snap = now;
-	st->target = target;
-}
-
-static void st_update(SMOOTHTIME *st, GRAPH *graph, int64 target, int time_left, int adjust_direction)
-{
-	int update_timer = 1;
-	
-	if(time_left < 0)
-	{
-		int is_spike = 0;
-		if(time_left < -50)
-		{
-			is_spike = 1;
-			
-			st->spikecounter += 5;
-			if(st->spikecounter > 50)
-				st->spikecounter = 50;
-		}
-		
-		if(is_spike && st->spikecounter < 15)
-		{
-			/* ignore this ping spike */
-			update_timer = 0;
-			graph_add(graph, time_left, 1,1,0);
-		}
-		else
-		{
-			graph_add(graph, time_left, 1,0,0);
-			if(st->adjustspeed[adjust_direction] < 30.0f)
-				st->adjustspeed[adjust_direction] *= 2.0f;
-		}
-	}
-	else
-	{
-		if(st->spikecounter)
-			st->spikecounter--;
-			
-		graph_add(graph, time_left, 0,1,0);
-		
-		st->adjustspeed[adjust_direction] *= 0.95f;
-		if(st->adjustspeed[adjust_direction] < 2.0f)
-			st->adjustspeed[adjust_direction] = 2.0f;
-	}
-	
-	last_input_timeleft = time_left;
-	
-	if(update_timer)
-		st_update_int(st, target);
-}
-
-static SMOOTHTIME game_time;
-static SMOOTHTIME predicted_time;
-
-/* graphs */
-static GRAPH inputtime_margin_graph;
-static GRAPH gametime_margin_graph;
-static GRAPH fps_graph;
-
-/* -- snapshot handling --- */
-enum
-{
-	NUM_SNAPSHOT_TYPES=2
-};
-
-/* the game snapshots are modifiable by the game */
-CSnapshotStorage snapshot_storage;
-static CSnapshotStorage::CHolder *snapshots[NUM_SNAPSHOT_TYPES] = {0, 0};
-
-static int recived_snapshots = 0;
-static char snapshot_incomming_data[CSnapshot::MAX_SIZE];
-
-static CSnapshotStorage::CHolder demorec_snapshotholders[NUM_SNAPSHOT_TYPES];
-static char *demorec_snapshotdata[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE];
-
-/* --- */
-
-void *snap_get_item(int snapid, int index, SNAP_ITEM *item)
-{
-	CSnapshotItem *i;
-	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
-	i = snapshots[snapid]->m_pAltSnap->GetItem(index);
-	item->datasize = snapshots[snapid]->m_pAltSnap->GetItemSize(index);
-	item->type = i->Type();
-	item->id = i->ID();
-	return (void *)i->Data();
-}
-
-void snap_invalidate_item(int snapid, int index)
-{
-	CSnapshotItem *i;
-	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
-	i = snapshots[snapid]->m_pAltSnap->GetItem(index);
-	if(i)
-	{
-		if((char *)i < (char *)snapshots[snapid]->m_pAltSnap || (char *)i > (char *)snapshots[snapid]->m_pAltSnap + snapshots[snapid]->m_SnapSize)
-			dbg_msg("ASDFASDFASdf", "ASDFASDFASDF");
-		if((char *)i >= (char *)snapshots[snapid]->m_pSnap && (char *)i < (char *)snapshots[snapid]->m_pSnap + snapshots[snapid]->m_SnapSize)
-			dbg_msg("ASDFASDFASdf", "ASDFASDFASDF");
-		i->m_TypeAndID = -1;
-	}
-}
-
-void *snap_find_item(int snapid, int type, int id)
-{
-	/* TODO: linear search. should be fixed. */
-	int i;
-	
-	if(!snapshots[snapid])
-		return 0x0;
-	
-	for(i = 0; i < snapshots[snapid]->m_pSnap->m_NumItems; i++)
-	{
-		CSnapshotItem *itm = snapshots[snapid]->m_pAltSnap->GetItem(i);
-		if(itm->Type() == type && itm->ID() == id)
-			return (void *)itm->Data();
-	}
-	return 0x0;
-}
-
-int snap_num_items(int snapid)
-{
-	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
-	if(!snapshots[snapid])
-		return 0;
-	return snapshots[snapid]->m_pSnap->m_NumItems;
-}
-
-/* ------ time functions ------ */
-float client_intratick() { return intratick; }
-float client_predintratick() { return predintratick; }
-float client_ticktime() { return ticktime; }
-int client_tick() { return current_tick; }
-int client_prevtick() { return prev_tick; }
-int client_predtick() { return current_predtick; }
-int client_tickspeed() { return SERVER_TICK_SPEED; }
-float client_frametime() { return frametime; }
-float client_localtime() { return (time_get()-local_start_time)/(float)(time_freq()); }
-
-/* ----- send functions ----- */
-int client_send_msg()
-{
-	const MSG_INFO *pInfo = msg_get_info();
-	CNetChunk Packet;
-	
-	if(!pInfo)
-		return -1;
-
-	if(client_state() == CLIENTSTATE_OFFLINE)
-		return 0;
-	
-	mem_zero(&Packet, sizeof(CNetChunk));
-	
-	Packet.m_ClientID = 0;
-	Packet.m_pData = pInfo->data;
-	Packet.m_DataSize = pInfo->size;
-
-	if(pInfo->flags&MSGFLAG_VITAL)
-		Packet.m_Flags |= NETSENDFLAG_VITAL;
-	if(pInfo->flags&MSGFLAG_FLUSH)
-		Packet.m_Flags |= NETSENDFLAG_FLUSH;
-		
-	if(pInfo->flags&MSGFLAG_RECORD)
-	{
-		if(demorec_isrecording())
-			demorec_record_message(Packet.m_pData, Packet.m_DataSize);
-	}
-
-	if(!(pInfo->flags&MSGFLAG_NOSEND))
-		m_NetClient.Send(&Packet);
-	return 0;
-}
-
-static void client_send_info()
-{
-	msg_pack_start_system(NETMSG_INFO, MSGFLAG_VITAL|MSGFLAG_FLUSH);
-	msg_pack_string(modc_net_version(), 128);
-	msg_pack_string(config.player_name, 128);
-	msg_pack_string(config.clan_name, 128);
-	msg_pack_string(config.password, 128);
-	msg_pack_end();
-	client_send_msg();
-}
-
-
-static void client_send_entergame()
-{
-	msg_pack_start_system(NETMSG_ENTERGAME, MSGFLAG_VITAL|MSGFLAG_FLUSH);
-	msg_pack_end();
-	client_send_msg();
-}
-
-static void client_send_ready()
-{
-	msg_pack_start_system(NETMSG_READY, MSGFLAG_VITAL|MSGFLAG_FLUSH);
-	msg_pack_end();
-	client_send_msg();
-}
-
-int client_rcon_authed()
-{
-	return rcon_authed;
-}
-
-void client_rcon_auth(const char *name, const char *password)
-{
-	msg_pack_start_system(NETMSG_RCON_AUTH, MSGFLAG_VITAL);
-	msg_pack_string(name, 32);
-	msg_pack_string(password, 32);
-	msg_pack_end();
-	client_send_msg();
-}
-
-void client_rcon(const char *cmd)
-{
-	msg_pack_start_system(NETMSG_RCON_CMD, MSGFLAG_VITAL);
-	msg_pack_string(cmd, 256);
-	msg_pack_end();
-	client_send_msg();
-}
-
-int client_connection_problems()
-{
-	return m_NetClient.GotProblems();
-}
-
-void client_direct_input(int *input, int size)
-{
-	int i;
-	msg_pack_start_system(NETMSG_INPUT, 0);
-	msg_pack_int(ack_game_tick);
-	msg_pack_int(current_predtick);
-	msg_pack_int(size);
-	
-	for(i = 0; i < size/4; i++)
-		msg_pack_int(input[i]);	
-		
-	msg_pack_end();
-	client_send_msg();
-}
-
-
-static void client_send_input()
-{
-	int64 now = time_get();	
-	int i, size;
-
-	if(current_predtick <= 0)
-		return;
-	 
-	/* fetch input */
-	size = modc_snap_input(inputs[current_input].data);
-	
-	if(!size)
-		return;
-	
-	/* pack input */
-	msg_pack_start_system(NETMSG_INPUT, MSGFLAG_FLUSH);
-	msg_pack_int(ack_game_tick);
-	msg_pack_int(current_predtick);
-	msg_pack_int(size);
-
-	inputs[current_input].tick = current_predtick;
-	inputs[current_input].predicted_time = st_get(&predicted_time, now);
-	inputs[current_input].time = now;
-
-	/* pack it */	
-	for(i = 0; i < size/4; i++)
-		msg_pack_int(inputs[current_input].data[i]);
-	
-	current_input++;
-	current_input%=200;
-	
-	msg_pack_end();
-	client_send_msg();
-}
-
-const char *client_latestversion()
-{
-	return versionstr;
-}
-
-/* TODO: OPT: do this alot smarter! */
-int *client_get_input(int tick)
-{
-	int i;
-	int best = -1;
-	for(i = 0; i < 200; i++)
-	{
-		if(inputs[i].tick <= tick && (best == -1 || inputs[best].tick < inputs[i].tick))
-			best = i;
-	}
-	
-	if(best != -1)
-		return (int *)inputs[best].data;
-	return 0;
-}
-
-/* ------ state handling ----- */
-static int state = CLIENTSTATE_OFFLINE;
-int client_state() { return state; }
-static void client_set_state(int s)
-{
-	int old = state;
-	if(config.debug)
-		dbg_msg("client", "state change. last=%d current=%d", state, s);
-	state = s;
-	if(old != s)
-		modc_statechange(state, old);
-}
-
-/* called when the map is loaded and we should init for a new round */
-static void client_on_enter_game()
-{
-	/* reset input */
-	int i;
-	for(i = 0; i < 200; i++)
-		inputs[i].tick = -1;
-	current_input = 0;
-
-	/* reset snapshots */
-	snapshots[SNAP_CURRENT] = 0;
-	snapshots[SNAP_PREV] = 0;
-	snapshot_storage.PurgeAll();
-	recived_snapshots = 0;
-	snapshot_parts = 0;
-	current_predtick = 0;
-	current_recv_tick = 0;
-}
-
-void client_entergame()
-{
-	if(state == CLIENTSTATE_DEMOPLAYBACK)
-		return;
-		
-	/* now we will wait for two snapshots */
-	/* to finish the connection */
-	client_send_entergame();
-	client_on_enter_game();
-}
-
-void client_connect(const char *server_address_str)
-{
-	char buf[512];
-	const char *port_str = 0;
-	int k;
-	int port = 8303;
-	
-	client_disconnect();
-
-	dbg_msg("client", "connecting to '%s'", server_address_str);
-
-	//client_serverinfo_request();
-	str_copy(buf, server_address_str, sizeof(buf));
-
-	for(k = 0; buf[k]; k++)
-	{
-		if(buf[k] == ':')
-		{
-			port_str = &(buf[k+1]);
-			buf[k] = 0;
-			break;
-		}
-	}
-	
-	if(port_str)
-		port = atoi(port_str);
-	
-	/* TODO: IPv6 support */
-	if(net_host_lookup(buf, &server_address, NETTYPE_IPV4) != 0)
-		dbg_msg("client", "could not find the address of %s, connecting to localhost", buf);
-	
-	rcon_authed = 0;
-	server_address.port = port;
-	m_NetClient.Connect(&server_address);
-	client_set_state(CLIENTSTATE_CONNECTING);
-	
-	graph_init(&inputtime_margin_graph, -150.0f, 150.0f);
-	graph_init(&gametime_margin_graph, -150.0f, 150.0f);
-}
-
-void client_disconnect_with_reason(const char *reason)
-{
-	/* stop demo playback */
-	demorec_playback_stop();
-	
-	/* */
-	rcon_authed = 0;
-	m_NetClient.Disconnect(reason);
-	client_set_state(CLIENTSTATE_OFFLINE);
-	map_unload();
-	
-	/* disable all downloads */
-	mapdownload_chunk = 0;
-	if(mapdownload_file)
-		io_close(mapdownload_file);
-	mapdownload_file = 0;
-	mapdownload_crc = 0;
-	mapdownload_totalsize = -1;
-	mapdownload_amount = 0;
-
-	/* clear the current server info */
-	mem_zero(&current_server_info, sizeof(current_server_info));
-	mem_zero(&server_address, sizeof(server_address));
-	
-	/* clear snapshots */
-	snapshots[SNAP_CURRENT] = 0;
-	snapshots[SNAP_PREV] = 0;
-	recived_snapshots = 0;
-}
-
-void client_disconnect()
-{
-	client_disconnect_with_reason(0);
-}
-
-
-void client_serverinfo(SERVER_INFO *serverinfo)
-{
-	mem_copy(serverinfo, &current_server_info, sizeof(current_server_info));
-}
-
-void client_serverinfo_request()
-{
-	mem_zero(&current_server_info, sizeof(current_server_info));
-	current_server_info_requesttime = 0;
-}
-
-static int client_load_data()
-{
-	debug_font = Graphics()->LoadTexture("debug_font.png", IMG_AUTO, TEXLOAD_NORESAMPLE);
-	return 1;
-}
-
-extern int snapshot_data_rate[0xffff];
-extern int snapshot_data_updates[0xffff];
-
-const char *modc_getitemname(int type);
-
-static void client_debug_render()
-{
-	static NETSTATS prev, current;
-	static int64 last_snap = 0;
-	static float frametime_avg = 0;
-	int64 now = time_get();
-	char buffer[512];
-	
-	if(!config.debug)
-		return;
-	
-	//m_pGraphics->BlendNormal();
-	Graphics()->TextureSet(debug_font);
-	Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
-	
-	if(time_get()-last_snap > time_freq())
-	{
-		last_snap = time_get();
-		prev = current;
-		net_stats(&current);
-	}
-	
-	/*
-		eth = 14
-		ip = 20
-		udp = 8
-		total = 42
-	*/
-	frametime_avg = frametime_avg*0.9f + frametime*0.1f;
-	str_format(buffer, sizeof(buffer), "ticks: %8d %8d mem %dk %d  gfxmem: N/A  fps: %3d",
-		current_tick, current_predtick,
-		mem_stats()->allocated/1024,
-		mem_stats()->total_allocations,
-		/*gfx_memory_usage()/1024, */ // TODO: Refactor: Reenable this
-		(int)(1.0f/frametime_avg));
-	Graphics()->QuadsText(2, 2, 16, 1,1,1,1, buffer);
-
-	
-	{
-		int send_packets = (current.sent_packets-prev.sent_packets);
-		int send_bytes = (current.sent_bytes-prev.sent_bytes);
-		int send_total = send_bytes + send_packets*42;
-		int recv_packets = (current.recv_packets-prev.recv_packets);
-		int recv_bytes = (current.recv_bytes-prev.recv_bytes);
-		int recv_total = recv_bytes + recv_packets*42;
-		
-		if(!send_packets) send_packets++;
-		if(!recv_packets) recv_packets++;
-		str_format(buffer, sizeof(buffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
-			send_packets, send_bytes, send_packets*42, send_total, (send_total*8)/1024, send_bytes/send_packets,
-			recv_packets, recv_bytes, recv_packets*42, recv_total, (recv_total*8)/1024, recv_bytes/recv_packets);
-		Graphics()->QuadsText(2, 14, 16, 1,1,1,1, buffer);
-	}
-	
-	/* render rates */
-	{
-		int y = 0;
-		int i;
-		for(i = 0; i < 256; i++)
-		{
-			if(snapshot_data_rate[i])
-			{
-				str_format(buffer, sizeof(buffer), "%4d %20s: %8d %8d %8d", i, modc_getitemname(i), snapshot_data_rate[i]/8, snapshot_data_updates[i],
-					(snapshot_data_rate[i]/snapshot_data_updates[i])/8);
-				Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, buffer);
-				y++;
-			}
-		}
-	}
-
-	str_format(buffer, sizeof(buffer), "pred: %d ms  %3.2f", 
-		(int)((st_get(&predicted_time, now)-st_get(&game_time, now))*1000/(float)time_freq()),
-		predicted_time.adjustspeed[1]);
-	Graphics()->QuadsText(2, 70, 16, 1,1,1,1, buffer);
-	
-	/* render graphs */
-	if(config.dbg_graphs)
-	{
-		//Graphics()->MapScreen(0,0,400.0f,300.0f);
-		float w = Graphics()->ScreenWidth()/4.0f;
-		float h = Graphics()->ScreenHeight()/6.0f;
-		float sp = Graphics()->ScreenWidth()/100.0f;
-		float x = Graphics()->ScreenWidth()-w-sp;
-
-		graph_scale_max(&fps_graph);
-		graph_scale_min(&fps_graph);
-		graph_render(&fps_graph, x, sp*5, w, h, "FPS");
-		graph_render(&inputtime_margin_graph, x, sp*5+h+sp, w, h, "Prediction Margin");
-		graph_render(&gametime_margin_graph, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
-	}
-}
-
-void client_quit()
-{
-	client_set_state(CLIENTSTATE_QUITING);
-}
-
-const char *client_error_string()
-{
-	return m_NetClient.ErrorString();
-}
-
-static void client_render()
-{
-	if(config.gfx_clear)	
-		Graphics()->Clear(1,1,0);
-
-	modc_render();
-	client_debug_render();
-}
-
-static const char *client_load_map(const char *name, const char *filename, int wanted_crc)
-{
-	static char errormsg[128];
-	DATAFILE *df;
-	int crc;
-	
-	client_set_state(CLIENTSTATE_LOADING);
-	
-	df = datafile_load(filename);
-	if(!df)
-	{
-		str_format(errormsg, sizeof(errormsg), "map '%s' not found", filename);
-		return errormsg;
-	}
-	
-	/* get the crc of the map */
-	crc = datafile_crc(filename);
-	if(crc != wanted_crc)
-	{
-		datafile_unload(df);
-		str_format(errormsg, sizeof(errormsg), "map differs from the server. %08x != %08x", crc, wanted_crc);
-		return errormsg;
-	}
-	
-	// stop demo recording if we loaded a new map
-	demorec_record_stop();
-	
-	dbg_msg("client", "loaded map '%s'", filename);
-	recived_snapshots = 0;
-	map_set(df);
-	
-	str_copy(current_map, name, sizeof(current_map));
-	current_map_crc = crc;
-	
-	return NULL;
-}
-
-static const char *client_load_map_search(const char *mapname, int wanted_crc)
-{
-	const char *error = 0;
-	char buf[512];
-	dbg_msg("client", "loading map, map=%s wanted crc=%08x", mapname, wanted_crc);
-	client_set_state(CLIENTSTATE_LOADING);
-	
-	/* try the normal maps folder */
-	str_format(buf, sizeof(buf), "maps/%s.map", mapname);
-	error = client_load_map(mapname, buf, wanted_crc);
-	if(!error)
-		return error;
-
-	/* try the downloaded maps */
-	str_format(buf, sizeof(buf), "downloadedmaps/%s_%8x.map", mapname, wanted_crc);
-	error = client_load_map(mapname, buf, wanted_crc);
-	return error;
-}
-
-static int player_score_comp(const void *a, const void *b)
-{
-	SERVER_INFO_PLAYER *p0 = (SERVER_INFO_PLAYER *)a;
-	SERVER_INFO_PLAYER *p1 = (SERVER_INFO_PLAYER *)b;
-	if(p0->score == p1->score)
-		return 0;
-	if(p0->score < p1->score)
-		return 1;
-	return -1;
-}
-
-static void client_process_packet(CNetChunk *pPacket)
-{
-	if(pPacket->m_ClientID == -1)
-	{
-		/* connectionlesss */
-		if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) &&
-			memcmp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
-		{
-			unsigned char *versiondata = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
-			int version_match = !memcmp(versiondata, VERSION_DATA, sizeof(VERSION_DATA));
-			
-			dbg_msg("client/version", "version does %s (%d.%d.%d)",
-				version_match ? "match" : "NOT match",
-				versiondata[1], versiondata[2], versiondata[3]);
-			
-			/* assume version is out of date when version-data doesn't match */
-			if (!version_match)
-			{
-				sprintf(versionstr, "%d.%d.%d", versiondata[1], versiondata[2], versiondata[3]);
-			}
-		}
-		
-		if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) &&
-			memcmp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
-		{
-			int size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST);
-			int num = size/sizeof(MASTERSRV_ADDR);
-			MASTERSRV_ADDR *addrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST));
-			int i;
-
-			for(i = 0; i < num; i++)
-			{
-				NETADDR addr;
-				
-				/* convert address */
-				mem_zero(&addr, sizeof(addr));
-				addr.type = NETTYPE_IPV4;
-				addr.ip[0] = addrs[i].ip[0];
-				addr.ip[1] = addrs[i].ip[1];
-				addr.ip[2] = addrs[i].ip[2];
-				addr.ip[3] = addrs[i].ip[3];
-				addr.port = (addrs[i].port[1]<<8) | addrs[i].port[0];
-				
-				client_serverbrowse_set(&addr, BROWSESET_MASTER_ADD, -1, NULL);
-			}
-		}
-
-		{
-			int packet_type = 0;
-			if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
-				packet_type = 2;
-
-			if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && memcmp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0)
-				packet_type = 1;
-			
-			if(packet_type)
-			{
-				/* we got ze info */
-				CUnpacker up;
-				SERVER_INFO info = {0};
-				int i;
-				int token = -1;
-				
-				up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO));
-				if(packet_type >= 2)
-					token = atol(up.GetString());
-				str_copy(info.version, up.GetString(), sizeof(info.version));
-				str_copy(info.name, up.GetString(), sizeof(info.name));
-				str_copy(info.map, up.GetString(), sizeof(info.map));
-				str_copy(info.gametype, up.GetString(), sizeof(info.gametype));
-				info.flags = atol(up.GetString());
-				info.progression = atol(up.GetString());
-				info.num_players = atol(up.GetString());
-				info.max_players = atol(up.GetString());
-				str_format(info.address, sizeof(info.address), "%d.%d.%d.%d:%d",
-					pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2],
-					pPacket->m_Address.ip[3], pPacket->m_Address.port);
-				
-				for(i = 0; i < info.num_players; i++)
-				{
-					str_copy(info.players[i].name, up.GetString(), sizeof(info.players[i].name));
-					info.players[i].score = atol(up.GetString());
-				}
-				
-				if(!up.Error())
-				{
-					/* sort players */
-					qsort(info.players, info.num_players, sizeof(*info.players), player_score_comp);
-					
-					if(net_addr_comp(&server_address, &pPacket->m_Address) == 0)
-					{
-						mem_copy(&current_server_info, &info, sizeof(current_server_info));
-						current_server_info.netaddr = server_address;
-						current_server_info_requesttime = -1;
-					}
-					else
-					{
-						if(packet_type == 2)
-							client_serverbrowse_set(&pPacket->m_Address, BROWSESET_TOKEN, token, &info);
-						else
-							client_serverbrowse_set(&pPacket->m_Address, BROWSESET_OLD_INTERNET, -1, &info);
-					}
-				}
-			}
-		}
-	}
-	else
-	{
-		int sys;
-		int msg = msg_unpack_start(pPacket->m_pData, pPacket->m_DataSize, &sys);
-		
-		if(sys)
-		{
-			/* system message */
-			if(msg == NETMSG_MAP_CHANGE)
-			{
-				const char *map = msg_unpack_string();
-				int map_crc = msg_unpack_int();
-				const char *error = 0;
-				int i;
-
-				if(msg_unpack_error())
-					return;
-				
-				for(i = 0; map[i]; i++) /* protect the player from nasty map names */
-				{
-					if(map[i] == '/' || map[i] == '\\')
-						error = "strange character in map name";
-				}
-				
-				if(error)
-					client_disconnect_with_reason(error);
-				else
-				{
-					error = client_load_map_search(map, map_crc);
-
-					if(!error)
-					{
-						dbg_msg("client/network", "loading done");
-						client_send_ready();
-						modc_connected();
-					}
-					else
-					{
-						str_format(mapdownload_filename, sizeof(mapdownload_filename), "downloadedmaps/%s_%08x.map", map, map_crc);
-
-						dbg_msg("client/network", "starting to download map to '%s'", mapdownload_filename);
-						
-						mapdownload_chunk = 0;
-						str_copy(mapdownload_name, map, sizeof(mapdownload_name));
-						mapdownload_file = engine_openfile(mapdownload_filename, IOFLAG_WRITE);
-						mapdownload_crc = map_crc;
-						mapdownload_totalsize = -1;
-						mapdownload_amount = 0;
-						
-						msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH);
-						msg_pack_int(mapdownload_chunk);
-						msg_pack_end();
-						client_send_msg();
-										
-						if(config.debug)
-							dbg_msg("client/network", "requested chunk %d", mapdownload_chunk);
-					}
-				}
-			}
-			else if(msg == NETMSG_MAP_DATA)
-			{
-				int last = msg_unpack_int();
-				int total_size = msg_unpack_int();
-				int size = msg_unpack_int();
-				const unsigned char *data = msg_unpack_raw(size);
-				
-				/* check fior errors */
-				if(msg_unpack_error() || size <= 0 || total_size <= 0 || !mapdownload_file)
-					return;
-				
-				io_write(mapdownload_file, data, size);
-				
-				mapdownload_totalsize = total_size;
-				mapdownload_amount += size;
-				
-				if(last)
-				{
-					const char *error;
-					dbg_msg("client/network", "download complete, loading map");
-					
-					io_close(mapdownload_file);
-					mapdownload_file = 0;
-					mapdownload_amount = 0;
-					mapdownload_totalsize = -1;
-					
-					/* load map */
-					error = client_load_map(mapdownload_name, mapdownload_filename, mapdownload_crc);
-					if(!error)
-					{
-						dbg_msg("client/network", "loading done");
-						client_send_ready();
-						modc_connected();
-					}
-					else
-						client_disconnect_with_reason(error);
-				}
-				else
-				{
-					/* request new chunk */
-					mapdownload_chunk++;
-					msg_pack_start_system(NETMSG_REQUEST_MAP_DATA, MSGFLAG_VITAL|MSGFLAG_FLUSH);
-					msg_pack_int(mapdownload_chunk);
-					msg_pack_end();
-					client_send_msg();
-
-					if(config.debug)
-						dbg_msg("client/network", "requested chunk %d", mapdownload_chunk);
-				}
-			}
-			else if(msg == NETMSG_PING)
-			{
-				msg_pack_start_system(NETMSG_PING_REPLY, 0);
-				msg_pack_end();
-				client_send_msg();
-			}
-			else if(msg == NETMSG_RCON_AUTH_STATUS)
-			{
-				int result = msg_unpack_int();
-				if(msg_unpack_error() == 0)
-					rcon_authed = result;
-			}
-			else if(msg == NETMSG_RCON_LINE)
-			{
-				const char *line = msg_unpack_string();
-				if(msg_unpack_error() == 0)
-				{
-					/*dbg_msg("remote", "%s", line);*/
-					modc_rcon_line(line);
-				}
-			}
-			else if(msg == NETMSG_PING_REPLY)
-				dbg_msg("client/network", "latency %.2f", (time_get() - ping_start_time)*1000 / (float)time_freq());
-			else if(msg == NETMSG_INPUTTIMING)
-			{
-				int input_predtick = msg_unpack_int();
-				int time_left = msg_unpack_int();
-				
-				/* adjust our prediction time */
-				int k;
-				int64 target = 0;
-				for(k = 0; k < 200; k++)
-				{
-					if(inputs[k].tick == input_predtick)
-					{
-						target = inputs[k].predicted_time + (time_get() - inputs[k].time);
-						target = target - (int64)(((time_left-prediction_margin)/1000.0f)*time_freq());
-						//st_update(&predicted_time, );
-						break;
-					}
-				}
-				
-				if(target)
-					st_update(&predicted_time, &inputtime_margin_graph, target, time_left, 1);
-			}
-			else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSINGLE || msg == NETMSG_SNAPEMPTY)
-			{
-				/*dbg_msg("client/network", "got snapshot"); */
-				int num_parts = 1;
-				int part = 0;
-				int game_tick = msg_unpack_int();
-				int delta_tick = game_tick-msg_unpack_int();
-				int part_size = 0;
-				int crc = 0;
-				int complete_size = 0;
-				const char *data = 0;
-				
-				/* we are not allowed to process snapshot yet */
-				if(client_state() < CLIENTSTATE_LOADING)
-					return;
-				
-				if(msg == NETMSG_SNAP)
-				{
-					num_parts = msg_unpack_int();
-					part = msg_unpack_int();
-				}
-				
-				if(msg != NETMSG_SNAPEMPTY)
-				{
-					crc = msg_unpack_int();
-					part_size = msg_unpack_int();
-				}
-				
-				data = (const char *)msg_unpack_raw(part_size);
-				
-				if(msg_unpack_error())
-					return;
-					
-				if(game_tick >= current_recv_tick)
-				{
-					if(game_tick != current_recv_tick)
-					{
-						snapshot_parts = 0;
-						current_recv_tick = game_tick;
-					}
-						
-					/* TODO: clean this up abit */
-					mem_copy((char*)snapshot_incomming_data + part*MAX_SNAPSHOT_PACKSIZE, data, part_size);
-					snapshot_parts |= 1<<part;
-				
-					if(snapshot_parts == (unsigned)((1<<num_parts)-1))
-					{
-						static CSnapshot emptysnap;
-						CSnapshot *deltashot = &emptysnap;
-						int purgetick;
-						void *deltadata;
-						int deltasize;
-						unsigned char tmpbuffer2[CSnapshot::MAX_SIZE];
-						unsigned char tmpbuffer3[CSnapshot::MAX_SIZE];
-						int snapsize;
-						
-						complete_size = (num_parts-1) * MAX_SNAPSHOT_PACKSIZE + part_size;
-
-						/* reset snapshoting */
-						snapshot_parts = 0;
-						
-						/* find snapshot that we should use as delta */
-						emptysnap.m_DataSize = 0;
-						emptysnap.m_NumItems = 0;
-						
-						/* find delta */
-						if(delta_tick >= 0)
-						{
-							int deltashot_size = snapshot_storage.Get(delta_tick, 0, &deltashot, 0);
-							
-							if(deltashot_size < 0)
-							{
-								/* couldn't find the delta snapshots that the server used */
-								/* to compress this snapshot. force the server to resync */
-								if(config.debug)
-									dbg_msg("client", "error, couldn't find the delta snapshot");
-								
-								/* ack snapshot */
-								/* TODO: combine this with the input message */
-								ack_game_tick = -1;
-								return;
-							}
-						}
-
-						/* decompress snapshot */
-						deltadata = CSnapshot::EmptyDelta();
-						deltasize = sizeof(int)*3;
-
-						if(complete_size)
-						{	
-							int intsize = intpack_decompress(snapshot_incomming_data, complete_size, tmpbuffer2);
-
-							if(intsize < 0) /* failure during decompression, bail */
-								return;
-
-							deltadata = tmpbuffer2;
-							deltasize = intsize;
-						}
-						
-						/* unpack delta */
-						purgetick = delta_tick;
-						snapsize = CSnapshot::UnpackDelta(deltashot, (CSnapshot*)tmpbuffer3, deltadata, deltasize);
-						if(snapsize < 0)
-						{
-							dbg_msg("client", "delta unpack failed!");
-							return;
-						}
-						
-						if(msg != NETMSG_SNAPEMPTY && ((CSnapshot*)tmpbuffer3)->Crc() != crc)
-						{
-							if(config.debug)
-							{
-								dbg_msg("client", "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
-									snapcrcerrors, game_tick, crc, ((CSnapshot*)tmpbuffer3)->Crc(), complete_size, delta_tick);
-							}
-								
-							snapcrcerrors++;
-							if(snapcrcerrors > 10)
-							{
-								/* to many errors, send reset */
-								ack_game_tick = -1;
-								client_send_input();
-								snapcrcerrors = 0;
-							}
-							return;
-						}
-						else
-						{
-							if(snapcrcerrors)
-								snapcrcerrors--;
-						}
-
-						/* purge old snapshots */
-						purgetick = delta_tick;
-						if(snapshots[SNAP_PREV] && snapshots[SNAP_PREV]->m_Tick < purgetick)
-							purgetick = snapshots[SNAP_PREV]->m_Tick;
-						if(snapshots[SNAP_CURRENT] && snapshots[SNAP_CURRENT]->m_Tick < purgetick)
-							purgetick = snapshots[SNAP_PREV]->m_Tick;
-						snapshot_storage.PurgeUntil(purgetick);
-						
-						/* add new */
-						snapshot_storage.Add(game_tick, time_get(), snapsize, (CSnapshot*)tmpbuffer3, 1);
-
-						/* add snapshot to demo */
-						if(demorec_isrecording())
-						{
-
-							/* write tick marker */
-							/*
-							DEMOREC_TICKMARKER marker;
-							marker.tick = game_tick;
-							swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int));
-							demorec_record_write("TICK", sizeof(marker), &marker);
-							demorec_record_write("SNAP", snapsize, tmpbuffer3);
-							*/
-							
-							/* write snapshot */
-							demorec_record_snapshot(game_tick, tmpbuffer3, snapsize);
-						}
-						
-						/* apply snapshot, cycle pointers */
-						recived_snapshots++;
-
-						current_recv_tick = game_tick;
-						
-						/* we got two snapshots until we see us self as connected */
-						if(recived_snapshots == 2)
-						{
-							/* start at 200ms and work from there */
-							st_init(&predicted_time, game_tick*time_freq()/50);
-							predicted_time.adjustspeed[1] = 1000.0f;
-							st_init(&game_time, (game_tick-1)*time_freq()/50);
-							snapshots[SNAP_PREV] = snapshot_storage.m_pFirst;
-							snapshots[SNAP_CURRENT] = snapshot_storage.m_pLast;
-							local_start_time = time_get();
-							client_set_state(CLIENTSTATE_ONLINE);
-						}
-
-						/* adjust game time */
-						{
-							int64 now = st_get(&game_time, time_get());
-							int64 tickstart = game_tick*time_freq()/50;
-							int64 time_left = (tickstart-now)*1000 / time_freq();
-							/*st_update(&game_time, (game_tick-1)*time_freq()/50);*/
-							st_update(&game_time, &gametime_margin_graph, (game_tick-1)*time_freq()/50, time_left, 0);
-						}
-						
-						/* ack snapshot */
-						ack_game_tick = game_tick;
-					}
-				}
-			}
-		}
-		else
-		{
-			/* game message */
-			if(demorec_isrecording())
-				demorec_record_message(pPacket->m_pData, pPacket->m_DataSize);
-				/* demorec_record_write("MESG", pPacket->data_size, ); */
-
-			modc_message(msg);
-		}
-	}
-}
-
-int client_mapdownload_amount() { return mapdownload_amount; }
-int client_mapdownload_totalsize() { return mapdownload_totalsize; }
-
-static void client_pump_network()
-{
-
-	m_NetClient.Update();
-
-	if(client_state() != CLIENTSTATE_DEMOPLAYBACK)
-	{
-		/* check for errors */
-		if(client_state() != CLIENTSTATE_OFFLINE && m_NetClient.State() == NETSTATE_OFFLINE)
-		{
-			client_set_state(CLIENTSTATE_OFFLINE);
-			client_disconnect();
-			dbg_msg("client", "offline error='%s'", m_NetClient.ErrorString());
-		}
-
-		/* */
-		if(client_state() == CLIENTSTATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE)
-		{
-			/* we switched to online */
-			dbg_msg("client", "connected, sending info");
-			client_set_state(CLIENTSTATE_LOADING);
-			client_send_info();
-		}
-	}
-	
-	/* process packets */
-	CNetChunk Packet;
-	while(m_NetClient.Recv(&Packet))
-		client_process_packet(&Packet);
-}
-
-static void client_democallback_snapshot(void *pData, int Size)
-{
-	/* update ticks, they could have changed */
-	const DEMOREC_PLAYBACKINFO *info = demorec_playback_info();			
-	CSnapshotStorage::CHolder *temp;
-	current_tick = info->current_tick;
-	prev_tick = info->previous_tick;
-	
-	/* handle snapshots */
-	temp = snapshots[SNAP_PREV];
-	snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-	snapshots[SNAP_CURRENT] = temp;
-	
-	mem_copy(snapshots[SNAP_CURRENT]->m_pSnap, pData, Size);
-	mem_copy(snapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size);
-	
-	modc_newsnapshot();
-	/*modc_predict();*/
-}
-
-static void client_democallback_message(void *data, int size)
-{
-	int sys = 0;
-	int msg = msg_unpack_start(data, size, &sys);
-	if(!sys)
-		modc_message(msg);
-}
-
-
-const DEMOPLAYBACK_INFO *client_demoplayer_getinfo()
-{
-	static DEMOPLAYBACK_INFO ret;
-	const DEMOREC_PLAYBACKINFO *info = demorec_playback_info();
-	ret.first_tick = info->first_tick;
-	ret.last_tick = info->last_tick;
-	ret.current_tick = info->current_tick;
-	ret.paused = info->paused;
-	ret.speed = info->speed;
-	return &ret;
-}
-
-void client_demoplayer_setpos(float percent)
-{
-	demorec_playback_set(percent);
-}
-
-void client_demoplayer_setspeed(float speed)
-{
-	demorec_playback_setspeed(speed);
-}
-
-void client_demoplayer_setpause(int paused)
-{
-	if(paused)
-		demorec_playback_pause();
-	else
-		demorec_playback_unpause();
-}
-
-static void client_update()
-{
-	if(client_state() == CLIENTSTATE_DEMOPLAYBACK)
-	{
-		demorec_playback_update();
-		if(demorec_isplaying())
-		{
-			/* update timers */
-			const DEMOREC_PLAYBACKINFO *info = demorec_playback_info();			
-			current_tick = info->current_tick;
-			prev_tick = info->previous_tick;
-			intratick = info->intratick;
-			ticktime = info->ticktime;
-		}
-		else
-		{
-			/* disconnect on error */
-			client_disconnect();
-		}
-	}
-	else if(client_state() != CLIENTSTATE_OFFLINE && recived_snapshots >= 3)
-	{
-		/* switch snapshot */
-		int repredict = 0;
-		int64 freq = time_freq();
-		int64 now = st_get(&game_time, time_get());
-		int64 pred_now = st_get(&predicted_time, time_get());
-
-		while(1)
-		{
-			CSnapshotStorage::CHolder *cur = snapshots[SNAP_CURRENT];
-			int64 tickstart = (cur->m_Tick)*time_freq()/50;
-
-			if(tickstart < now)
-			{
-				CSnapshotStorage::CHolder *next = snapshots[SNAP_CURRENT]->m_pNext;
-				if(next)
-				{
-					snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-					snapshots[SNAP_CURRENT] = next;
-					
-					/* set ticks */
-					current_tick = snapshots[SNAP_CURRENT]->m_Tick;
-					prev_tick = snapshots[SNAP_PREV]->m_Tick;
-					
-					if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
-					{
-						modc_newsnapshot();
-						repredict = 1;
-					}
-				}
-				else
-					break;
-			}
-			else
-				break;
-		}
-
-		if(snapshots[SNAP_CURRENT] && snapshots[SNAP_PREV])
-		{
-			int64 curtick_start = (snapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50;
-			int64 prevtick_start = (snapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
-			/*tg_add(&predicted_time_graph, pred_now, 0); */
-			int prev_pred_tick = (int)(pred_now*50/time_freq());
-			int new_pred_tick = prev_pred_tick+1;
-			static float last_predintra = 0;
-
-			intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
-			ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/
-
-			curtick_start = new_pred_tick*time_freq()/50;
-			prevtick_start = prev_pred_tick*time_freq()/50;
-			predintratick = (pred_now - prevtick_start) / (float)(curtick_start-prevtick_start);
-			
-			if(new_pred_tick < snapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || new_pred_tick > snapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
-			{
-				dbg_msg("client", "prediction time reset!");
-				st_init(&predicted_time, snapshots[SNAP_CURRENT]->m_Tick*time_freq()/50);
-			}
-			
-			if(new_pred_tick > current_predtick)
-			{
-				last_predintra = predintratick;
-				current_predtick = new_pred_tick;
-				repredict = 1;
-				
-				/* send input */
-				client_send_input();
-			}
-			
-			last_predintra = predintratick;
-		}
-
-		/* only do sane predictions */
-		if(repredict)
-		{
-			if(current_predtick > current_tick && current_predtick < current_tick+50)
-				modc_predict();
-		}
-		
-		/* fetch server info if we don't have it */
-		if(client_state() >= CLIENTSTATE_LOADING &&
-			current_server_info_requesttime >= 0 &&
-			time_get() > current_server_info_requesttime)
-		{
-			client_serverbrowse_request(&server_address);
-			current_server_info_requesttime = time_get()+time_freq()*2;
-		}
-	}
-
-	/* STRESS TEST: join the server again */
-	if(config.dbg_stress)
-	{
-		static int64 action_taken = 0;
-		int64 now = time_get();
-		if(client_state() == CLIENTSTATE_OFFLINE)
-		{
-			if(now > action_taken+time_freq()*2)
-			{
-				dbg_msg("stress", "reconnecting!");
-				client_connect(config.dbg_stress_server);
-				action_taken = now;
-			}
-		}
-		else
-		{
-			/*if(now > action_taken+time_freq()*(10+config.dbg_stress))
-			{
-				dbg_msg("stress", "disconnecting!");
-				client_disconnect();
-				action_taken = now;
-			}*/
-		}
-	}
-	
-	/* pump the network */
-	client_pump_network();
-	
-	/* update the maser server registry */
-	mastersrv_update();
-	
-	/* update the server browser */
-	client_serverbrowse_update();
-}
-
-
-static void client_versionupdate()
-{
-	static int state = 0;
-	static HOSTLOOKUP version_serveraddr;
-	
-	if(state == 0)
-	{
-		engine_hostlookup(&version_serveraddr, config.cl_version_server);
-		state++;
-	}
-	else if(state == 1)
-	{
-		if(jobs_status(&version_serveraddr.job) == JOBSTATUS_DONE)
-		{
-			CNetChunk Packet;
-			
-			mem_zero(&Packet, sizeof(Packet));
-			
-			version_serveraddr.addr.port = VERSIONSRV_PORT;
-			
-			Packet.m_ClientID = -1;
-			Packet.m_Address = version_serveraddr.addr;
-			Packet.m_pData = VERSIONSRV_GETVERSION;
-			Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION);
-			Packet.m_Flags = NETSENDFLAG_CONNLESS;
-			
-			m_NetClient.Send(&Packet);
-			state++;
-		}
-	}
-}
-
-static void client_run()
-{
-	NETADDR bindaddr;
-	int64 reporttime = time_get();
-	int64 reportinterval = time_freq()*1;
-
-	static PERFORMACE_INFO rootscope = {"root", 0};
-	perf_start(&rootscope);
-
-	local_start_time = time_get();
-	snapshot_parts = 0;
-	
-	/* init graphics and sound */
-	m_pGraphics = CreateEngineGraphics();
-	if(m_pGraphics->Init() != 0)
-		return;
-
-	/* start refreshing addresses while we load */
-	mastersrv_refresh_addresses();
-	
-	/* init the editor */
-	m_pEditor = CreateEditor();
-	m_pEditor->Init(m_pGraphics);
-
-	/* sound is allowed to fail */
-	snd_init();
-
-	/* load data */
-	if(!client_load_data())
-		return;
-
-	/* init the mod */
-	m_pGameClient = CreateGameClient(&m_Client);
-	modc_init();
-	dbg_msg("client", "version %s", modc_net_version());
-	
-	/* open socket */
-	mem_zero(&bindaddr, sizeof(bindaddr));
-	m_NetClient.Open(bindaddr, 0);
-	
-	/* connect to the server if wanted */
-	/*
-	if(config.cl_connect[0] != 0)
-		client_connect(config.cl_connect);
-	config.cl_connect[0] = 0;
-	*/
-
-	/* */
-	graph_init(&fps_graph, 0.0f, 200.0f);
-	
-	/* never start with the editor */
-	config.cl_editor = 0;
-		
-	inp_mouse_mode_relative();
-	
-	while (1)
-	{	
-		static PERFORMACE_INFO rootscope = {"root", 0};
-		int64 frame_start_time = time_get();
-		frames++;
-		
-		perf_start(&rootscope);
-
-		/* */
-		client_versionupdate();
-		
-		/* handle pending connects */
-		if(cmd_connect[0])
-		{
-			client_connect(cmd_connect);
-			cmd_connect[0] = 0;
-		}
-		
-		/* update input */
-		{
-			static PERFORMACE_INFO scope = {"inp_update", 0};
-			perf_start(&scope);
-			inp_update();
-			perf_end();
-		}
-
-		/* update sound */		
-		{
-			static PERFORMACE_INFO scope = {"snd_update", 0};
-			perf_start(&scope);
-			snd_update();
-			perf_end();
-		}
-		
-		/* release focus */
-		if(!Graphics()->WindowActive())
-		{
-			if(window_must_refocus == 0)
-				inp_mouse_mode_absolute();
-			window_must_refocus = 1;
-		}
-		else if (config.dbg_focus && inp_key_pressed(KEY_ESCAPE))
-		{
-			inp_mouse_mode_absolute();
-			window_must_refocus = 1;
-		}
-
-		/* refocus */
-		if(window_must_refocus && Graphics()->WindowActive())
-		{
-			if(window_must_refocus < 3)
-			{
-				inp_mouse_mode_absolute();
-				window_must_refocus++;
-			}
-
-			if(inp_key_pressed(KEY_MOUSE_1))
-			{
-				inp_mouse_mode_relative();
-				window_must_refocus = 0;
-			}
-		}
-
-		/* panic quit button */
-		if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_pressed('q'))
-			break;
-
-		if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('d'))
-			config.debug ^= 1;
-
-		if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('g'))
-			config.dbg_graphs ^= 1;
-
-		if(inp_key_pressed(KEY_LCTRL) && inp_key_pressed(KEY_LSHIFT) && inp_key_down('e'))
-		{
-			config.cl_editor = config.cl_editor^1;
-			inp_mouse_mode_relative();
-		}
-		
-		/*
-		if(!gfx_window_open())
-			break;
-		*/
-			
-		/* render */
-		if(config.cl_editor)
-		{
-			client_update();
-			m_pEditor->UpdateAndRender();
-			m_pGraphics->Swap();
-		}
-		else
-		{
-			{
-				static PERFORMACE_INFO scope = {"client_update", 0};
-				perf_start(&scope);
-				client_update();
-				perf_end();
-			}
-			
-			if(config.dbg_stress)
-			{
-				if((frames%10) == 0)
-				{
-					client_render();
-					m_pGraphics->Swap();
-				}
-			}
-			else
-			{
-				{
-					static PERFORMACE_INFO scope = {"client_render", 0};
-					perf_start(&scope);
-					client_render();
-					perf_end();
-				}
-
-				{
-					static PERFORMACE_INFO scope = {"gfx_swap", 0};
-					perf_start(&scope);
-					m_pGraphics->Swap();
-					perf_end();
-				}
-			}
-		}
-
-		perf_end();
-
-		
-		/* check conditions */
-		if(client_state() == CLIENTSTATE_QUITING)
-			break;
-
-		/* be nice */
-		if(config.dbg_stress)
-			thread_sleep(5);
-		else if(config.cl_cpu_throttle || !Graphics()->WindowActive())
-			thread_sleep(1);
-			
-		if(config.dbg_hitch)
-		{
-			thread_sleep(config.dbg_hitch);
-			config.dbg_hitch = 0;
-		}
-		
-		if(reporttime < time_get())
-		{
-			if(0 && config.debug)
-			{
-				dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
-					frames/(float)(reportinterval/time_freq()),
-					1.0f/frametime_high,
-					1.0f/frametime_low,
-					m_NetClient.State());
-			}
-			frametime_low = 1;
-			frametime_high = 0;
-			frames = 0;
-			reporttime += reportinterval;
-			perf_next();
-			
-			if(config.dbg_pref)
-				perf_dump(&rootscope);
-		}
-		
-		/* update frametime */
-		frametime = (time_get()-frame_start_time)/(float)time_freq();
-		if(frametime < frametime_low)
-			frametime_low = frametime;
-		if(frametime > frametime_high)
-			frametime_high = frametime;
-		
-		graph_add(&fps_graph, 1.0f/frametime, 1,1,1);
-	}
-	
-	modc_shutdown();
-	client_disconnect();
-
-	m_pGraphics->Shutdown();
-	snd_shutdown();
-}
-
-void gfx_swap()
-{
-	m_pGraphics->Swap();
-}
-
-static void con_connect(void *result, void *user_data)
-{
-	str_copy(cmd_connect, console_arg_string(result, 0), sizeof(cmd_connect));
-}
-
-static void con_disconnect(void *result, void *user_data)
-{
-	client_disconnect();
-}
-
-static void con_quit(void *result, void *user_data)
-{
-	client_quit();
-}
-
-static void con_ping(void *result, void *user_data)
-{
-	msg_pack_start_system(NETMSG_PING, 0);
-	msg_pack_end();
-	client_send_msg();
-	ping_start_time = time_get();
-}
-
-static void con_screenshot(void *result, void *user_data)
-{
-	Graphics()->TakeScreenshot();
-}
-
-static void con_rcon(void *result, void *user_data)
-{
-	client_rcon(console_arg_string(result, 0));
-}
-
-static void con_rcon_auth(void *result, void *user_data)
-{
-	client_rcon_auth("", console_arg_string(result, 0));
-}
-
-static void con_addfavorite(void *result, void *user_data)
-{
-	NETADDR addr;
-	if(net_addr_from_str(&addr, console_arg_string(result, 0)) == 0)
-		client_serverbrowse_addfavorite(addr);
-}
-
-const char *client_demoplayer_play(const char *filename)
-{
-	int crc;
-	const char *error;
-	client_disconnect();
-	m_NetClient.ResetErrorString();
-	
-	/* try to start playback */
-	demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message);
-	
-	if(demorec_playback_load(filename))
-		return "error loading demo";
-	
-	/* load map */
-	crc = (demorec_playback_info()->header.crc[0]<<24)|
-		(demorec_playback_info()->header.crc[1]<<16)|
-		(demorec_playback_info()->header.crc[2]<<8)|
-		(demorec_playback_info()->header.crc[3]);
-	error = client_load_map_search(demorec_playback_info()->header.map, crc);
-	if(error)
-	{
-		client_disconnect_with_reason(error);	
-		return error;
-	}
-	
-	modc_connected();
-	
-	/* setup buffers */	
-	mem_zero(demorec_snapshotdata, sizeof(demorec_snapshotdata));
-
-	snapshots[SNAP_CURRENT] = &demorec_snapshotholders[SNAP_CURRENT];
-	snapshots[SNAP_PREV] = &demorec_snapshotholders[SNAP_PREV];
-	
-	snapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][0];
-	snapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_CURRENT][1];
-	snapshots[SNAP_CURRENT]->m_SnapSize = 0;
-	snapshots[SNAP_CURRENT]->m_Tick = -1;
-	
-	snapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][0];
-	snapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)demorec_snapshotdata[SNAP_PREV][1];
-	snapshots[SNAP_PREV]->m_SnapSize = 0;
-	snapshots[SNAP_PREV]->m_Tick = -1;
-
-	/* enter demo playback state */
-	client_set_state(CLIENTSTATE_DEMOPLAYBACK);
-	
-	demorec_playback_play();
-	modc_entergame();
-	
-	return 0;
-}
-
-static void con_play(void *result, void *user_data)
-{
-	client_demoplayer_play(console_arg_string(result, 0));
-}
-
-static void con_record(void *result, void *user_data)
-{
-	if(state != CLIENTSTATE_ONLINE)
-		dbg_msg("demorec/record", "client is not online");
-	else
-	{
-		char filename[512];
-		str_format(filename, sizeof(filename), "demos/%s.demo", console_arg_string(result, 0));
-		demorec_record_start(filename, modc_net_version(), current_map, current_map_crc, "client");
-	}
-}
-
-static void con_stoprecord(void *result, void *user_data)
-{
-	demorec_record_stop();
-}
-
-static void con_serverdummy(void *result, void *user_data)
-{
-	dbg_msg("client", "this command is not available on the client");
-}
-
-static void client_register_commands()
-{
-	MACRO_REGISTER_COMMAND("quit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds");
-	MACRO_REGISTER_COMMAND("exit", "", CFGFLAG_CLIENT, con_quit, 0x0, "Quit Teeworlds");
-	MACRO_REGISTER_COMMAND("connect", "s", CFGFLAG_CLIENT, con_connect, 0x0, "Connect to the specified host/ip");
-	MACRO_REGISTER_COMMAND("disconnect", "", CFGFLAG_CLIENT, con_disconnect, 0x0, "Disconnect from the server");
-	MACRO_REGISTER_COMMAND("ping", "", CFGFLAG_CLIENT, con_ping, 0x0, "Ping the current server");
-	MACRO_REGISTER_COMMAND("screenshot", "", CFGFLAG_CLIENT, con_screenshot, 0x0, "Take a screenshot");
-	MACRO_REGISTER_COMMAND("rcon", "r", CFGFLAG_CLIENT, con_rcon, 0x0, "Send specified command to rcon");
-	MACRO_REGISTER_COMMAND("rcon_auth", "s", CFGFLAG_CLIENT, con_rcon_auth, 0x0, "Authenticate to rcon");
-
-	MACRO_REGISTER_COMMAND("play", "r", CFGFLAG_CLIENT, con_play, 0x0, "Play the file specified");
-	MACRO_REGISTER_COMMAND("record", "s", CFGFLAG_CLIENT, con_record, 0, "Record to the file");
-	MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_CLIENT, con_stoprecord, 0, "Stop recording");
-
-	MACRO_REGISTER_COMMAND("add_favorite", "s", CFGFLAG_CLIENT, con_addfavorite, 0x0, "Add a server as a favorite");
-	
-	/* register server dummy commands for tab completion */
-	MACRO_REGISTER_COMMAND("kick", "i", CFGFLAG_SERVER, con_serverdummy, 0, "Kick player with specified id");
-	MACRO_REGISTER_COMMAND("ban", "s?i", CFGFLAG_SERVER, con_serverdummy, 0, "Ban player with ip/id for x minutes");
-	MACRO_REGISTER_COMMAND("unban", "s", CFGFLAG_SERVER, con_serverdummy, 0, "Unban ip");
-	MACRO_REGISTER_COMMAND("bans", "", CFGFLAG_SERVER, con_serverdummy, 0, "Show banlist");
-	MACRO_REGISTER_COMMAND("status", "", CFGFLAG_SERVER, con_serverdummy, 0, "List players");
-	MACRO_REGISTER_COMMAND("shutdown", "", CFGFLAG_SERVER, con_serverdummy, 0, "Shut down");
-	/*MACRO_REGISTER_COMMAND("record", "", CFGFLAG_SERVER, con_serverdummy, 0);
-	MACRO_REGISTER_COMMAND("stoprecord", "", CFGFLAG_SERVER, con_serverdummy, 0);*/
-}
-
-void client_save_line(const char *line)
-{
-	engine_config_write_line(line);	
-}
-
-const char *client_user_directory()
-{
-	static char path[1024] = {0};
-	fs_storage_path("Teeworlds", path, sizeof(path));
-	return path;
-}
-
-#if defined(CONF_PLATFORM_MACOSX)
-int SDL_main(int argc, char **argv)
-#else
-int main(int argc, char **argv)
-#endif
-{
-	/* init the engine */
-	dbg_msg("client", "starting...");
-	engine_init("Teeworlds");
-	
-	/* register all console commands */
-	client_register_commands();
-	modc_console_init();
-	
-	/* parse the command line arguments */
-	engine_parse_arguments(argc, argv);
-
-	/* execute config file */
-	console_execute_file("settings.cfg");
-	
-	/* run the client*/
-	client_run();
-	
-	/* write down the config and quit */
-	if(engine_config_write_start() == 0)
-	{
-		config_save();
-		client_serverbrowse_save();
-		modc_save_config();
-		engine_config_write_stop();
-	}
-	
-	return 0;
-}
diff --git a/src/engine/client/ec_gfx.cpp b/src/engine/client/ec_gfx.cpp
deleted file mode 100644
index 5632581a..00000000
--- a/src/engine/client/ec_gfx.cpp
+++ /dev/null
@@ -1,992 +0,0 @@
-/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
-
-#include <base/detect.h>
-
-#include "SDL.h"
-
-#ifdef CONF_FAMILY_WINDOWS
-	#define WIN32_LEAN_AND_MEAN
-	#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
-
-#include <base/system.h>
-#include <engine/external/pnglite/pnglite.h>
-
-#include <engine/e_client_interface.h>
-#include <engine/e_engine.h>
-#include <engine/e_config.h>
-#include <engine/e_keys.h>
-
-#include <string.h>
-#include <stdio.h>
-#include <math.h>
-
-/* compressed textures */
-#define GL_COMPRESSED_RGB_ARB 0x84ED
-#define GL_COMPRESSED_RGBA_ARB 0x84EE
-#define GL_COMPRESSED_ALPHA_ARB 0x84E9
-
-#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
-
-
-void gfx_font_init();
-
-VIDEO_MODE fakemodes[] = {
-	{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}
-};
-
-int gfx_get_video_modes(VIDEO_MODE *list, int maxcount)
-{
-	int num_modes = sizeof(fakemodes)/sizeof(VIDEO_MODE);
-	SDL_Rect **modes;
-
-	if(config.gfx_display_all_modes)
-	{
-		int count = sizeof(fakemodes)/sizeof(VIDEO_MODE);
-		mem_copy(list, fakemodes, sizeof(fakemodes));
-		if(maxcount < count)
-			count = maxcount;
-		return count;
-	}
-	
-	/* TODO: fix this code on osx or windows */
-		
-	modes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN);
-	if(modes == NULL)
-	{
-		/* no modes */
-		num_modes = 0;
-	}
-	else if(modes == (SDL_Rect**)-1)
-	{
-		/* all modes */
-	}
-	else
-	{
-		int i;
-		num_modes = 0;
-		for(i = 0; modes[i]; ++i)
-		{
-			if(num_modes == maxcount)
-				break;
-			list[num_modes].width = modes[i]->w;
-			list[num_modes].height = modes[i]->h;
-			list[num_modes].red = 8;
-			list[num_modes].green = 8;
-			list[num_modes].blue = 8;
-			num_modes++;
-		}
-	}
-	
-	return num_modes;
-}
-
-
-#include "graphics.h"
-
-class CGraphics_OpenGL : public IEngineGraphics
-{
-protected:
-	/* */
-	typedef struct { float x, y, z; } CPoint;
-	typedef struct { float u, v; } CTexCoord;
-	typedef struct { float r, g, b, a; } CColor;
-
-	typedef struct
-	{
-		CPoint m_Pos;
-		CTexCoord m_Tex;
-		CColor m_Color;
-	} CVertex;
-	
-	enum
-	{
-		MAX_VERTICES = 32*1024,
-		MAX_TEXTURES = 1024*4,
-		
-		DRAWING_QUADS=1,
-		DRAWING_LINES=2		
-	};
-
-	CVertex m_aVertices[MAX_VERTICES];
-	int m_NumVertices;
-
-	CColor m_aColor[4];
-	CTexCoord m_aTexture[4];
-
-	bool m_RenderEnable;
-
-	float m_Rotation;
-	int m_Drawing;
-	bool m_DoScreenshot;
-
-	float m_ScreenX0;
-	float m_ScreenY0;
-	float m_ScreenX1;
-	float m_ScreenY1;
-
-	int m_InvalidTexture;
-
-	struct CTexture
-	{
-		GLuint tex;
-		int memsize;
-		int flags;
-		int next;
-	};
-
-	enum
-	{
-		
-	};
-
-	CTexture m_aTextures[MAX_TEXTURES];
-	int m_FirstFreeTexture;
-	int m_TextureMemoryUsage;
-
-
-	void Flush()
-	{
-		if(m_NumVertices == 0)
-			return;
-			
-		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-		glVertexPointer(3, GL_FLOAT,
-				sizeof(CVertex),
-				(char*)m_aVertices);
-		glTexCoordPointer(2, GL_FLOAT,
-				sizeof(CVertex),
-				(char*)m_aVertices + sizeof(float)*3);
-		glColorPointer(4, GL_FLOAT,
-				sizeof(CVertex),
-				(char*)m_aVertices + sizeof(float)*5);
-		glEnableClientState(GL_VERTEX_ARRAY);
-		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-		glEnableClientState(GL_COLOR_ARRAY);
-		
-		if(m_RenderEnable)
-		{
-			if(m_Drawing == DRAWING_QUADS)
-				glDrawArrays(GL_QUADS, 0, m_NumVertices);
-			else if(m_Drawing == DRAWING_LINES)
-				glDrawArrays(GL_LINES, 0, m_NumVertices);
-		}
-		
-		/* Reset pointer */
-		m_NumVertices = 0;
-	}
-
-	void AddVertices(int count)
-	{
-		m_NumVertices += count;
-		if((m_NumVertices + count) >= MAX_VERTICES)
-			Flush();
-	}
-	
-	void Rotate(CPoint *pCenter, CPoint *pPoint)
-	{
-		float x = pPoint->x - pCenter->x;
-		float y = pPoint->y - pCenter->y;
-		pPoint->x = x * cosf(m_Rotation) - y * sinf(m_Rotation) + pCenter->x;
-		pPoint->y = x * sinf(m_Rotation) + y * cosf(m_Rotation) + pCenter->y;
-	}
-	
-	
-
-
-	static unsigned char sample(int w, int h, const unsigned char *data, int u, int v, int offset)
-	{
-		return (data[(v*w+u)*4+offset]+
-		data[(v*w+u+1)*4+offset]+
-		data[((v+1)*w+u)*4+offset]+
-		data[((v+1)*w+u+1)*4+offset])/4;
-	}	
-public:
-	CGraphics_OpenGL()
-	{
-		m_NumVertices = 0;
-		
-		m_ScreenX0 = 0;
-		m_ScreenY0 = 0;
-		m_ScreenX1 = 0;
-		m_ScreenY1 = 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;
-	}
-	
-
-	virtual void ClipEnable(int x, int y, int w, int h)
-	{
-		//if(no_gfx) return;
-		glScissor(x, ScreenHeight()-(y+h), w, h);
-		glEnable(GL_SCISSOR_TEST);
-	}
-
-	virtual void ClipDisable()
-	{
-		//if(no_gfx) return;
-		glDisable(GL_SCISSOR_TEST);
-	}
-		
-
-	virtual void BlendNone()
-	{
-		glDisable(GL_BLEND);
-	}
-
-	virtual void BlendNormal()
-	{
-		glEnable(GL_BLEND);
-		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	}
-
-	virtual void BlendAdditive()
-	{
-		glEnable(GL_BLEND);
-		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
-	}
-
-	//int gfx_memory_usage() { return m_MemoryUsage; }	
-		
-	virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y)
-	{
-		m_ScreenX0 = tl_x;
-		m_ScreenY0 = tl_y;
-		m_ScreenX1 = br_x;
-		m_ScreenY1 = br_y;
-		glMatrixMode(GL_PROJECTION);
-		glLoadIdentity();
-		glOrtho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f);
-	}
-
-	virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y)
-	{
-		*tl_x = m_ScreenX0;
-		*tl_y = m_ScreenY0;
-		*br_x = m_ScreenX1;
-		*br_y = m_ScreenY1;
-	}
-
-	virtual void LinesBegin()
-	{
-		dbg_assert(m_Drawing == 0, "called begin twice");
-		m_Drawing = DRAWING_LINES;
-		SetColor(1,1,1,1);
-	}
-
-	virtual void LinesEnd()
-	{
-		dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin");
-		Flush();
-		m_Drawing = 0;
-	}
-
-	virtual void LinesDraw(float x0, float y0, float x1, float y1)
-	{
-		dbg_assert(m_Drawing == DRAWING_LINES, "called draw without begin");
-		
-		m_aVertices[m_NumVertices].m_Pos.x = x0;
-		m_aVertices[m_NumVertices].m_Pos.y = y0;
-		m_aVertices[m_NumVertices].m_Tex = m_aTexture[0];
-		m_aVertices[m_NumVertices].m_Color = m_aColor[0];
-
-		m_aVertices[m_NumVertices + 1].m_Pos.x = x1;
-		m_aVertices[m_NumVertices + 1].m_Pos.y = y1;
-		m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1];
-		m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1];
-		
-		AddVertices(2);
-	}
-	
-
-	
-	virtual int UnloadTexture(int Index)
-	{
-		if(Index == m_InvalidTexture)
-			return 0;
-			
-		if(Index < 0)
-			return 0;
-			
-		glDeleteTextures(1, &m_aTextures[Index].tex);
-		m_aTextures[Index].next = m_FirstFreeTexture;
-		m_TextureMemoryUsage -= m_aTextures[Index].memsize;
-		m_FirstFreeTexture = Index;
-		return 0;
-	}
-
-
-	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
-	{
-		int mipmap = 1;
-		unsigned char *texdata = (unsigned char *)pData;
-		unsigned char *tmpdata = 0;
-		int oglformat = 0;
-		int store_oglformat = 0;
-		int tex = 0;
-		
-		/* don't waste memory on texture if we are stress testing */
-		if(config.dbg_stress)
-			return 	m_InvalidTexture;
-		
-		/* grab texture */
-		tex = m_FirstFreeTexture;
-		m_FirstFreeTexture = m_aTextures[tex].next;
-		m_aTextures[tex].next = -1;
-		
-		/* resample if needed */
-		if(!(Flags&TEXLOAD_NORESAMPLE) && config.gfx_texture_quality==0)
-		{
-			if(Width > 16 && Height > 16 && Format == IMG_RGBA)
-			{
-				unsigned char *tmpdata;
-				int c = 0;
-				int x, y;
-
-				tmpdata = (unsigned char *)mem_alloc(Width*Height*4, 1);
-
-				Width/=2;
-				Height/=2;
-
-				for(y = 0; y < Height; y++)
-					for(x = 0; x < Width; x++, c++)
-					{
-						tmpdata[c*4] = sample(Width*2, Height*2, texdata, x*2,y*2, 0);
-						tmpdata[c*4+1] = sample(Width*2, Height*2, texdata, x*2,y*2, 1);
-						tmpdata[c*4+2] = sample(Width*2, Height*2, texdata, x*2,y*2, 2);
-						tmpdata[c*4+3] = sample(Width*2, Height*2, texdata, x*2,y*2, 3);
-					}
-				texdata = tmpdata;
-			}
-		}
-		
-		oglformat = GL_RGBA;
-		if(Format == IMG_RGB)
-			oglformat = GL_RGB;
-		else if(Format == IMG_ALPHA)
-			oglformat = GL_ALPHA;
-		
-		/* upload texture */
-		if(config.gfx_texture_compression)
-		{
-			store_oglformat = GL_COMPRESSED_RGBA_ARB;
-			if(StoreFormat == IMG_RGB)
-				store_oglformat = GL_COMPRESSED_RGB_ARB;
-			else if(StoreFormat == IMG_ALPHA)
-				store_oglformat = GL_COMPRESSED_ALPHA_ARB;
-		}
-		else
-		{
-			store_oglformat = GL_RGBA;
-			if(StoreFormat == IMG_RGB)
-				store_oglformat = GL_RGB;
-			else if(StoreFormat == IMG_ALPHA)
-				store_oglformat = GL_ALPHA;
-		}
-			
-		glGenTextures(1, &m_aTextures[tex].tex);
-		glBindTexture(GL_TEXTURE_2D, m_aTextures[tex].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, store_oglformat, Width, Height, oglformat, GL_UNSIGNED_BYTE, texdata);
-		
-		/* calculate memory usage */
-		{
-			int pixel_size = 4;
-			if(StoreFormat == IMG_RGB)
-				pixel_size = 3;
-			else if(StoreFormat == IMG_ALPHA)
-				pixel_size = 1;
-
-			m_aTextures[tex].memsize = Width*Height*pixel_size;
-			if(mipmap)
-			{
-				while(Width > 2 && Height > 2)
-				{
-					Width>>=1;
-					Height>>=1;
-					m_aTextures[tex].memsize += Width*Height*pixel_size;
-				}
-			}
-		}
-		
-		m_TextureMemoryUsage += m_aTextures[tex].memsize;
-		mem_free(tmpdata);
-		return tex;
-	}
-
-	/* simple uncompressed RGBA loaders */
-	virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags)
-	{
-		int l = strlen(pFilename);
-		int id;
-		IMAGE_INFO img;
-		
-		if(l < 3)
-			return -1;
-		if(LoadPNG(&img, pFilename))
-		{
-			if (StoreFormat == IMG_AUTO)
-				StoreFormat = img.format;
-
-			id = LoadTextureRaw(img.width, img.height, img.format, img.data, StoreFormat, Flags);
-			mem_free(img.data);
-			return id;
-		}
-		
-		return m_InvalidTexture;
-	}
-
-	virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename)
-	{
-		char aCompleteFilename[512];
-		unsigned char *pBuffer;
-		png_t png;
-		
-		/* open file for reading */
-		png_init(0,0);
-
-		engine_getpath(aCompleteFilename, sizeof(aCompleteFilename), pFilename, IOFLAG_READ);
-		
-		if(png_open_file(&png, aCompleteFilename) != PNG_NO_ERROR)
-		{
-			dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename);
-			return 0;
-		}
-		
-		if(png.depth != 8 || (png.color_type != PNG_TRUECOLOR && png.color_type != PNG_TRUECOLOR_ALPHA))
-		{
-			dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename);
-			png_close_file(&png);
-			return 0;
-		}
-			
-		pBuffer = (unsigned char *)mem_alloc(png.width * png.height * png.bpp, 1);
-		png_get_data(&png, pBuffer);
-		png_close_file(&png);
-		
-		pImg->width = png.width;
-		pImg->height = png.height;
-		if(png.color_type == PNG_TRUECOLOR)
-			pImg->format = IMG_RGB;
-		else if(png.color_type == PNG_TRUECOLOR_ALPHA)
-			pImg->format = IMG_RGBA;
-		pImg->data = pBuffer;
-		return 1;
-	}
-
-	void ScreenshotDirect(const char *filename)
-	{
-		/* fetch image data */
-		int y;
-		int w = m_ScreenWidth;
-		int h = m_ScreenHeight;
-		unsigned char *pixel_data = (unsigned char *)mem_alloc(w*(h+1)*4, 1);
-		unsigned char *temp_row = pixel_data+w*h*4;
-		glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data);
-		
-		/* flip the pixel because opengl works from bottom left corner */
-		for(y = 0; y < h/2; y++)
-		{
-			mem_copy(temp_row, pixel_data+y*w*4, w*4);
-			mem_copy(pixel_data+y*w*4, pixel_data+(h-y-1)*w*4, w*4);
-			mem_copy(pixel_data+(h-y-1)*w*4, temp_row,w*4);
-		}
-		
-		/* find filename */
-		{
-			char wholepath[1024];
-			png_t png;
-
-			engine_savepath(filename, wholepath, sizeof(wholepath));
-		
-			/* save png */
-			dbg_msg("client", "saved screenshot to '%s'", wholepath);
-			png_open_file_write(&png, wholepath);
-			png_set_data(&png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pixel_data);
-			png_close_file(&png);
-		}
-
-		/* clean up */
-		mem_free(pixel_data);
-	}
-
-	virtual void TextureSet(int TextureID)
-	{
-		dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin");
-		if(TextureID == -1)
-		{
-			glDisable(GL_TEXTURE_2D);
-		}
-		else
-		{
-			glEnable(GL_TEXTURE_2D);
-			glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].tex);
-		}
-	}
-
-	virtual void Clear(float r, float g, float b)
-	{
-		glClearColor(r,g,b,0.0f);
-		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	}
-
-	virtual void QuadsBegin()
-	{
-		dbg_assert(m_Drawing == 0, "called quads_begin twice");
-		m_Drawing = DRAWING_QUADS;
-		
-		QuadsSetSubset(0,0,1,1);
-		QuadsSetRotation(0);
-		SetColor(1,1,1,1);
-	}
-
-	virtual void QuadsEnd()
-	{
-		dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin");
-		Flush();
-		m_Drawing = 0;
-	}
-
-	virtual void QuadsSetRotation(float Angle)
-	{
-		dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin");
-		m_Rotation = Angle;
-	}
-
-	virtual void SetColorVertex(int i, float r, float g, float b, float a)
-	{
-		dbg_assert(m_Drawing != 0, "called gfx_quads_setcolorvertex without begin");
-		m_aColor[i].r = r;
-		m_aColor[i].g = g;
-		m_aColor[i].b = b;
-		m_aColor[i].a = a;
-	}
-
-	virtual void SetColor(float r, float g, float b, float a)
-	{
-		dbg_assert(m_Drawing != 0, "called gfx_quads_setcolor without begin");
-		SetColorVertex(0, r, g, b, a);
-		SetColorVertex(1, r, g, b, a);
-		SetColorVertex(2, r, g, b, a);
-		SetColorVertex(3, r, g, b, a);
-	}
-
-	virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v)
-	{
-		dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin");
-
-		m_aTexture[0].u = tl_u;	m_aTexture[1].u = br_u;
-		m_aTexture[0].v = tl_v;	m_aTexture[1].v = tl_v;
-
-		m_aTexture[3].u = tl_u;	m_aTexture[2].u = br_u;
-		m_aTexture[3].v = br_v;	m_aTexture[2].v = br_v;
-	}
-
-	virtual void 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;
-	}
-
-	virtual void QuadsDraw(float x, float y, float w, float h)
-	{
-		QuadsDrawTL(x-w/2, y-h/2,w,h);
-	}
-
-	virtual void QuadsDrawTL(float x, float y, float w, float h)
-	{
-		CPoint Center;
-
-		dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin");
-
-		Center.x = x + w/2;
-		Center.y = y + h/2;
-		Center.z = 0;
-		
-		m_aVertices[m_NumVertices].m_Pos.x = x;
-		m_aVertices[m_NumVertices].m_Pos.y = y;
-		m_aVertices[m_NumVertices].m_Tex = m_aTexture[0];
-		m_aVertices[m_NumVertices].m_Color = m_aColor[0];
-		Rotate(&Center, &m_aVertices[m_NumVertices].m_Pos);
-
-		m_aVertices[m_NumVertices + 1].m_Pos.x = x+w;
-		m_aVertices[m_NumVertices + 1].m_Pos.y = y;
-		m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1];
-		m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1];
-		Rotate(&Center, &m_aVertices[m_NumVertices + 1].m_Pos);
-
-		m_aVertices[m_NumVertices + 2].m_Pos.x = x + w;
-		m_aVertices[m_NumVertices + 2].m_Pos.y = y+h;
-		m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[2];
-		m_aVertices[m_NumVertices + 2].m_Color = m_aColor[2];
-		Rotate(&Center, &m_aVertices[m_NumVertices + 2].m_Pos);
-
-		m_aVertices[m_NumVertices + 3].m_Pos.x = x;
-		m_aVertices[m_NumVertices + 3].m_Pos.y = y+h;
-		m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[3];
-		m_aVertices[m_NumVertices + 3].m_Color = m_aColor[3];
-		Rotate(&Center, &m_aVertices[m_NumVertices + 3].m_Pos);
-		
-		AddVertices(4);
-	}
-
-	void QuadsDrawFreeform(
-		float x0, float y0, float x1, float y1,
-		float x2, float y2, float x3, float y3)
-	{
-		dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform without begin");
-		
-		m_aVertices[m_NumVertices].m_Pos.x = x0;
-		m_aVertices[m_NumVertices].m_Pos.y = y0;
-		m_aVertices[m_NumVertices].m_Tex = m_aTexture[0];
-		m_aVertices[m_NumVertices].m_Color = m_aColor[0];
-
-		m_aVertices[m_NumVertices + 1].m_Pos.x = x1;
-		m_aVertices[m_NumVertices + 1].m_Pos.y = y1;
-		m_aVertices[m_NumVertices + 1].m_Tex = m_aTexture[1];
-		m_aVertices[m_NumVertices + 1].m_Color = m_aColor[1];
-
-		m_aVertices[m_NumVertices + 2].m_Pos.x = x3;
-		m_aVertices[m_NumVertices + 2].m_Pos.y = y3;
-		m_aVertices[m_NumVertices + 2].m_Tex = m_aTexture[3];
-		m_aVertices[m_NumVertices + 2].m_Color = m_aColor[3];
-
-		m_aVertices[m_NumVertices + 3].m_Pos.x = x2;
-		m_aVertices[m_NumVertices + 3].m_Pos.y = y2;
-		m_aVertices[m_NumVertices + 3].m_Tex = m_aTexture[2];
-		m_aVertices[m_NumVertices + 3].m_Color = m_aColor[2];
-		
-		AddVertices(4);
-	}
-
-	virtual void 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);
-				
-				QuadsDrawTL(x,y,Size,Size);
-				x += Size/2;
-			}
-		}
-		
-		QuadsEnd();
-	}
-	
-	virtual bool Init()
-	{
-		/* 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].next = i+1;
-		m_aTextures[MAX_TEXTURES-1].next = -1;
-
-		/* 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);
-
-		/* 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,IMG_RGBA,aNullTextureData,IMG_RGBA,TEXLOAD_NORESAMPLE);
-		dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].tex);
-		
-		return true;
-	}
-};
-
-class CGraphics_SDL : public CGraphics_OpenGL
-{
-	SDL_Surface *m_pScreenSurface;	
-	
-	int TryInit()
-	{
-		const SDL_VideoInfo *pInfo;
-		int Flags = SDL_OPENGL;
-		
-		m_ScreenWidth = config.gfx_screen_width;
-		m_ScreenHeight = config.gfx_screen_height;
-
-		pInfo = SDL_GetVideoInfo();
-
-		/* set flags */
-		Flags  = SDL_OPENGL;
-		Flags |= SDL_GL_DOUBLEBUFFER;
-		Flags |= SDL_HWPALETTE;
-		if(config.dbg_resizable)
-			Flags |= SDL_RESIZABLE;
-
-		if(pInfo->hw_available)
-			Flags |= SDL_HWSURFACE;
-		else
-			Flags |= SDL_SWSURFACE;
-
-		if(pInfo->blit_hw)
-			Flags |= SDL_HWACCEL;
-
-		if(config.gfx_fullscreen)
-			Flags |= SDL_FULLSCREEN;
-
-		/* set gl attributes */
-		if(config.gfx_fsaa_samples)
-		{
-			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
-			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config.gfx_fsaa_samples);
-		}
-		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, config.gfx_vsync);
-
-		/* set caption */
-		SDL_WM_SetCaption("Teeworlds", "Teeworlds");
-		
-		/* create window */
-		m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags);
-		if(m_pScreenSurface == NULL)
-		{
-			dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError());
-			return -1;
-		}
-		
-		return 0;
-	}
-
-
-	int InitWindow()
-	{
-		if(TryInit() == 0)
-			return 0;
-		
-		/* try disabling fsaa */
-		while(config.gfx_fsaa_samples)
-		{
-			config.gfx_fsaa_samples--;
-			
-			if(config.gfx_fsaa_samples)
-				dbg_msg("gfx", "lowering FSAA to %d and trying again", config.gfx_fsaa_samples);
-			else
-				dbg_msg("gfx", "disabling FSAA and trying again");
-
-			if(TryInit() == 0)
-				return 0;
-		}
-
-		/* try lowering the resolution */
-		if(config.gfx_screen_width != 640 || config.gfx_screen_height != 480)
-		{
-			dbg_msg("gfx", "setting resolution to 640x480 and trying again");
-			config.gfx_screen_width = 640;
-			config.gfx_screen_height = 480;
-
-			if(TryInit() == 0)
-				return 0;
-		}
-
-		dbg_msg("gfx", "out of ideas. failed to init graphics");
-						
-		return -1;		
-	}
-
-	
-public:
-	CGraphics_SDL()
-	{
-		m_pScreenSurface = 0;
-	}
-
-	virtual bool Init()
-	{
-		{
-			int Systems = SDL_INIT_VIDEO;
-			
-			if(config.snd_enable)
-				Systems |= SDL_INIT_AUDIO;
-
-			if(config.cl_eventthread)
-				Systems |= SDL_INIT_EVENTTHREAD;
-			
-			if(SDL_Init(Systems) < 0)
-			{
-				dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError());
-				return -1;
-			}
-		}
-		
-		atexit(SDL_Quit);
-
-		#ifdef CONF_FAMILY_WINDOWS
-			if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED"))
-				putenv("SDL_VIDEO_WINDOW_POS=8,27");
-		#endif
-		
-		if(InitWindow() != 0)
-			return -1;
-
-		SDL_ShowCursor(0);
-			
-		CGraphics_OpenGL::Init();
-		
-		MapScreen(0,0,config.gfx_screen_width, config.gfx_screen_height);
-
-		/* init input */
-		inp_init();
-
-		/* font init */
-		gfx_font_init();
-
-		return 0;
-	}
-	
-	virtual void Shutdown()
-	{
-		/* TODO: SDL, is this correct? */
-		SDL_Quit();
-	}
-
-	virtual void Minimize()
-	{
-		SDL_WM_IconifyWindow();
-	}
-
-	virtual void Maximize()
-	{
-		/* TODO: SDL */
-	}
-
-	virtual int WindowActive()
-	{
-		return SDL_GetAppState()&SDL_APPINPUTFOCUS;
-	}
-
-	virtual int WindowOpen()
-	{
-		return SDL_GetAppState()&SDL_APPACTIVE;
-
-	}
-	
-	virtual void TakeScreenshot()
-	{
-		m_DoScreenshot = true;
-	}
-	
-	virtual void Swap()
-	{
-		if(m_DoScreenshot)
-		{
-			/* find filename */
-			char filename[128];
-			static int index = 1;
-
-			for(; index < 1000; index++)
-			{
-				IOHANDLE io;
-				str_format(filename, sizeof(filename), "screenshots/screenshot%04d.png", index);
-				io = engine_openfile(filename, IOFLAG_READ);
-				if(io)
-					io_close(io);
-				else
-					break;
-			}
-
-			ScreenshotDirect(filename);
-			m_DoScreenshot = false;
-		}
-		
-		{
-			static PERFORMACE_INFO pscope = {"glfwSwapBuffers", 0};
-			perf_start(&pscope);
-			SDL_GL_SwapBuffers();
-			perf_end();
-		}
-		
-		if(config.gfx_finish)
-			glFinish();		
-	}
-};
-
-extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); }
diff --git a/src/engine/client/ec_gfx_text.cpp b/src/engine/client/ec_gfx_text.cpp
deleted file mode 100644
index d17d1bed..00000000
--- a/src/engine/client/ec_gfx_text.cpp
+++ /dev/null
@@ -1,669 +0,0 @@
-#include <base/system.h>
-#include <string.h>
-#include <engine/e_client_interface.h>
-#include <engine/client/graphics.h>
-
-extern IEngineGraphics *Graphics();
-
-#ifdef CONF_PLATFORM_MACOSX
-	#include <OpenGL/gl.h>
-	#include <OpenGL/glu.h>
-#else
-	#include <GL/gl.h>
-	#include <GL/glu.h>
-#endif
-
-static int word_length(const char *text)
-{
-	int s = 1;
-	while(1)
-	{
-		if(*text == 0)
-			return s-1;
-		if(*text == '\n' || *text == '\t' || *text == ' ')
-			return s;
-		text++;
-		s++;
-	}
-}
-
-static float text_r=1;
-static float text_g=1;
-static float text_b=1;
-static float text_a=1;
-
-static struct FONT *default_font = 0;
-void gfx_text_set_default_font(struct FONT *font)
-{
-	default_font = font;
-}
-
-
-void gfx_text_set_cursor(TEXT_CURSOR *cursor, float x, float y, float font_size, int flags)
-{
-	mem_zero(cursor, sizeof(*cursor));
-	cursor->font_size = font_size;
-	cursor->start_x = x;
-	cursor->start_y = y;
-	cursor->x = x;
-	cursor->y = y;
-	cursor->line_count = 1;
-	cursor->line_width = -1;
-	cursor->flags = flags;
-	cursor->charcount = 0;
-}
-
-
-void gfx_text(void *font_set_v, float x, float y, float size, const char *text, int max_width)
-{
-	TEXT_CURSOR cursor;
-	gfx_text_set_cursor(&cursor, x, y, size, TEXTFLAG_RENDER);
-	cursor.line_width = max_width;
-	gfx_text_ex(&cursor, text, -1);
-}
-
-float gfx_text_width(void *font_set_v, float size, const char *text, int length)
-{
-	TEXT_CURSOR cursor;
-	gfx_text_set_cursor(&cursor, 0, 0, size, 0);
-	gfx_text_ex(&cursor, text, length);
-	return cursor.x;
-}
-
-void gfx_text_color(float r, float g, float b, float a)
-{
-	text_r = r;
-	text_g = g;
-	text_b = b;
-	text_a = a;
-}
-
-/* ft2 texture */
-#include <ft2build.h>
-#include FT_FREETYPE_H
-
-static FT_Library ft_library;
-
-#define MAX_CHARACTERS 64
-
-
-/* GL_LUMINANCE can be good for debugging*/
-static int font_texture_format = GL_ALPHA;
-
-
-static int font_sizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36};
-#define NUM_FONT_SIZES (sizeof(font_sizes)/sizeof(int))
-
-
-typedef struct FONTCHAR
-{
-	int id;
-	
-	/* these values are scaled to the font size */
-	/* width * font_size == real_size */
-	float width;
-	float height;
-	float offset_x;
-	float offset_y;
-	float advance_x;
-	
-	float uvs[4];
-	int64 touch_time;
-} FONTCHAR;
-
-typedef struct FONTSIZEDATA
-{
-	int font_size;
-	FT_Face *face;
-
-	unsigned textures[2];
-	int texture_width;
-	int texture_height;
-	
-	int num_x_chars;
-	int num_y_chars;
-	
-	int char_max_width;
-	int char_max_height;
-	
-	FONTCHAR characters[MAX_CHARACTERS*MAX_CHARACTERS];
-	
-	int current_character;	
-} FONTSIZEDATA;
-
-typedef struct FONT
-{
-	char filename[128];
-	FT_Face ft_face;
-	FONTSIZEDATA sizes[NUM_FONT_SIZES];
-} FONT;
-
-static int font_get_index(int pixelsize)
-{
-	for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
-	{
-		if(font_sizes[i] >= pixelsize)
-			return i;
-	}
-	
-	return NUM_FONT_SIZES-1;
-}
-
-FONT *gfx_font_load(const char *filename)
-{
-	FONT *font = (FONT *)mem_alloc(sizeof(FONT), 1);
-	
-	mem_zero(font, sizeof(*font));
-	str_copy(font->filename, filename, sizeof(font->filename));
-	
-	if(FT_New_Face(ft_library, font->filename, 0, &font->ft_face))
-	{
-		mem_free(font);
-		return NULL;
-	}
-
-	for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
-		font->sizes[i].font_size = -1;
-		
-	return font;
-};
-
-void gfx_font_destroy(FONT *font)
-{
-	mem_free(font);
-}
-
-void gfx_font_init()
-{
-	FT_Init_FreeType(&ft_library);
-}
-
-static void grow(unsigned char *in, unsigned char *out, int w, int h)
-{
-	int y, x;
-	for(y = 0; y < h; y++) 
-		for(x = 0; x < w; x++) 
-		{ 
-			int c = in[y*w+x]; 
-			int s_y, s_x;
-
-			for(s_y = -1; s_y <= 1; s_y++)
-				for(s_x = -1; s_x <= 1; s_x++)
-				{
-					int get_x = x+s_x;
-					int get_y = y+s_y;
-					if (get_x >= 0 && get_y >= 0 && get_x < w && get_y < h)
-					{
-						int index = get_y*w+get_x;
-						if(in[index] > c)
-							c = in[index]; 
-					}
-				}
-
-			out[y*w+x] = c;
-		}
-}
-
-static void font_init_texture(FONTSIZEDATA *sizedata, int charwidth, int charheight, int xchars, int ychars)
-{
-	static int font_memory_usage = 0;
-	int i;
-	int width = charwidth*xchars;
-	int height = charheight*ychars;
-	void *mem = mem_alloc(width*height, 1);
-	mem_zero(mem, width*height);
-	
-	if(sizedata->textures[0] == 0)
-		glGenTextures(2, sizedata->textures);
-	else
-		font_memory_usage -= sizedata->texture_width*sizedata->texture_height*2;
-	
-	sizedata->num_x_chars = xchars;
-	sizedata->num_y_chars = ychars;
-	sizedata->texture_width = width;
-	sizedata->texture_height = height;
-	sizedata->current_character = 0;
-	
-	for(i = 0; i < 2; i++)
-	{
-		glBindTexture(GL_TEXTURE_2D, sizedata->textures[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, font_texture_format, width, height, 0, font_texture_format, GL_UNSIGNED_BYTE, mem);
-		font_memory_usage += width*height;
-	}
-	
-	dbg_msg("", "font memory usage: %d", font_memory_usage);
-	
-	mem_free(mem);
-}
-
-static void font_increase_texture_size(FONTSIZEDATA *sizedata)
-{
-	if(sizedata->texture_width < sizedata->texture_height)
-		sizedata->num_x_chars <<= 1;
-	else
-		sizedata->num_y_chars <<= 1;
-	font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, sizedata->num_x_chars, sizedata->num_y_chars);		
-}
-
-static void font_init_index(FONT *font, int index)
-{
-	int outline_thickness = 1;
-	FONTSIZEDATA *sizedata = &font->sizes[index];
-	
-	sizedata->font_size = font_sizes[index];
-	FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size);
-	
-	if(sizedata->font_size >= 18)
-		outline_thickness = 2;
-		
-	{
-		unsigned glyph_index;
-		int charcode;
-		int max_h = 0;
-		int max_w = 0;
-		
-		charcode = FT_Get_First_Char(font->ft_face, &glyph_index);
-		while(glyph_index != 0)
-		{   
-			/* do stuff */
-			FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT);
-			
-			if(font->ft_face->glyph->metrics.width > max_w) max_w = font->ft_face->glyph->metrics.width;
-			if(font->ft_face->glyph->metrics.height > max_h) max_h = font->ft_face->glyph->metrics.height;
-			charcode = FT_Get_Next_Char(font->ft_face, charcode, &glyph_index);
-		}
-		
-		max_w = (max_w>>6)+2+outline_thickness*2;
-		max_h = (max_h>>6)+2+outline_thickness*2;
-		
-		for(sizedata->char_max_width = 1; sizedata->char_max_width < max_w; sizedata->char_max_width <<= 1);
-		for(sizedata->char_max_height = 1; sizedata->char_max_height < max_h; sizedata->char_max_height <<= 1);
-	}
-	
-	//dbg_msg("font", "init size %d, texture size %d %d", font->sizes[index].font_size, w, h);
-	//FT_New_Face(ft_library, "data/fonts/vera.ttf", 0, &font->ft_face);
-	font_init_texture(sizedata, sizedata->char_max_width, sizedata->char_max_height, 8, 8);
-}
-
-static FONTSIZEDATA *font_get_size(FONT *font, int pixelsize)
-{
-	int index = font_get_index(pixelsize);
-	if(font->sizes[index].font_size != font_sizes[index])
-		font_init_index(font, index);
-	return &font->sizes[index];
-}
-
-
-static void font_upload_glyph(FONTSIZEDATA *sizedata, int texnum, int slot_id, int chr, const void *data)
-{
-	int x = (slot_id%sizedata->num_x_chars) * (sizedata->texture_width/sizedata->num_x_chars);
-	int y = (slot_id/sizedata->num_x_chars) * (sizedata->texture_height/sizedata->num_y_chars);
-	
-	glBindTexture(GL_TEXTURE_2D, sizedata->textures[texnum]);
-	glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
-		sizedata->texture_width/sizedata->num_x_chars,
-		sizedata->texture_height/sizedata->num_y_chars,
-		font_texture_format, GL_UNSIGNED_BYTE, data);
-}
-
-/* 8k of data used for rendering glyphs */
-static unsigned char glyphdata[(4096/64) * (4096/64)];
-static unsigned char glyphdata_outlined[(4096/64) * (4096/64)];
-
-static int font_get_slot(FONTSIZEDATA *sizedata)
-{
-	int char_count = sizedata->num_x_chars*sizedata->num_y_chars;
-	if(sizedata->current_character < char_count)
-	{
-		int i = sizedata->current_character;
-		sizedata->current_character++;
-		return i;
-	}
-
-	/* kick out the oldest */
-	/* TODO: remove this linear search */
-	{
-		int oldest = 0;
-		int i;
-		for(i = 1; i < char_count; i++)
-		{
-			if(sizedata->characters[i].touch_time < sizedata->characters[oldest].touch_time)
-				oldest = i;
-		}
-		
-		if(time_get()-sizedata->characters[oldest].touch_time < time_freq())
-		{
-			font_increase_texture_size(sizedata);
-			return font_get_slot(sizedata);
-		}
-		
-		return oldest;
-	}
-}
-
-static int font_render_glyph(FONT *font, FONTSIZEDATA *sizedata, int chr)
-{
-	FT_Bitmap *bitmap;
-	int slot_id = 0;
-	int slot_w = sizedata->texture_width / sizedata->num_x_chars;
-	int slot_h = sizedata->texture_height / sizedata->num_y_chars;
-	int slot_size = slot_w*slot_h;
-	int outline_thickness = 1;
-	int x = 1;
-	int y = 1;
-	int px, py;
-
-	FT_Set_Pixel_Sizes(font->ft_face, 0, sizedata->font_size);
-
-	if(FT_Load_Char(font->ft_face, chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP))
-	{
-		dbg_msg("font", "error loading glyph %d", chr);
-		return -1;
-	}
-
-	bitmap = &font->ft_face->glyph->bitmap; 
-	
-	/* fetch slot */
-	slot_id = font_get_slot(sizedata);
-	if(slot_id < 0)
-		return -1;
-	
-	/* adjust spacing */
-	if(sizedata->font_size >= 18)
-		outline_thickness = 2;
-	x += outline_thickness;
-	y += outline_thickness;
-
-	/* prepare glyph data */
-	mem_zero(glyphdata, slot_size);
-
-	if(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY)
-	{
-		for(py = 0; py < bitmap->rows; py++) 
-			for(px = 0; px < bitmap->width; px++) 
-				glyphdata[(py+y)*slot_w+px+x] = bitmap->buffer[py*bitmap->pitch+px];
-	}
-	else if(bitmap->pixel_mode == FT_PIXEL_MODE_MONO)
-	{
-		for(py = 0; py < bitmap->rows; py++) 
-			for(px = 0; px < bitmap->width; px++)
-			{
-				if(bitmap->buffer[py*bitmap->pitch+px/8]&(1<<(7-(px%8))))
-					glyphdata[(py+y)*slot_w+px+x] = 255;
-			}
-	}
-
-	if(0) for(py = 0; py < slot_w; py++) 
-		for(px = 0; px < slot_h; px++) 
-			glyphdata[py*slot_w+px] = 255;
-	
-	/* upload the glyph */
-	font_upload_glyph(sizedata, 0, slot_id, chr, glyphdata);
-	
-	if(outline_thickness == 1)
-	{
-		grow(glyphdata, glyphdata_outlined, slot_w, slot_h);
-		font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata_outlined);
-	}
-	else
-	{
-		grow(glyphdata, glyphdata_outlined, slot_w, slot_h);
-		grow(glyphdata_outlined, glyphdata, slot_w, slot_h);
-		font_upload_glyph(sizedata, 1, slot_id, chr, glyphdata);
-	}
-	
-	/* set char info */
-	{
-		FONTCHAR *fontchr = &sizedata->characters[slot_id];
-		float scale = 1.0f/sizedata->font_size;
-		float uscale = 1.0f/sizedata->texture_width;
-		float vscale = 1.0f/sizedata->texture_height;
-		int height = bitmap->rows + outline_thickness*2 + 2;
-		int width = bitmap->width + outline_thickness*2 + 2;
-		
-		fontchr->id = chr;
-		fontchr->height = height * scale;
-		fontchr->width = width * scale;
-		fontchr->offset_x = (font->ft_face->glyph->bitmap_left-1) * scale;
-		fontchr->offset_y = (sizedata->font_size - font->ft_face->glyph->bitmap_top) * scale;
-		fontchr->advance_x = (font->ft_face->glyph->advance.x>>6) * scale;
-		
-		fontchr->uvs[0] = (slot_id%sizedata->num_x_chars) / (float)(sizedata->num_x_chars);
-		fontchr->uvs[1] = (slot_id/sizedata->num_x_chars) / (float)(sizedata->num_y_chars);
-		fontchr->uvs[2] = fontchr->uvs[0] + width*uscale;
-		fontchr->uvs[3] = fontchr->uvs[1] + height*vscale;
-	}
-	
-	return slot_id;
-}
-
-static FONTCHAR *font_get_char(FONT *font, FONTSIZEDATA *sizedata, int chr)
-{
-	FONTCHAR *fontchr = NULL;
-	
-	/* search for the character */
-	/* TODO: remove this linear search */
-	int i;
-	for(i = 0; i < sizedata->current_character; i++)
-	{
-		if(sizedata->characters[i].id == chr)
-		{
-			fontchr = &sizedata->characters[i];
-			break;
-		}
-	}
-	
-	/* check if we need to render the character */
-	if(!fontchr)
-	{
-		int index = font_render_glyph(font, sizedata, chr);
-		if(index >= 0)
-			fontchr = &sizedata->characters[index];
-	}
-	
-	/* touch the character */
-	/* TODO: don't call time_get here */
-	if(fontchr)
-		fontchr->touch_time = time_get();
-		
-	return fontchr;
-}
-
-/* must only be called from the rendering function as the font must be set to the correct size */
-static void font_render_setup(FONT *font, int size)
-{
-	FT_Set_Pixel_Sizes(font->ft_face, 0, size);
-}
-
-static float font_kerning(FONT *font, int left, int right)
-{
-	FT_Vector kerning = {0,0};
-	FT_Get_Kerning(font->ft_face, left, right, FT_KERNING_DEFAULT, &kerning);
-	return (kerning.x>>6);
-}
-
-
-void gfx_text_ex(TEXT_CURSOR *cursor, const char *text, int length)
-{
-	FONT *font = cursor->font;
-	FONTSIZEDATA *sizedata = NULL;
-
-	float screen_x0, screen_y0, screen_x1, screen_y1;
-	float fake_to_screen_x, fake_to_screen_y;
-	int actual_x, actual_y;
-
-	int actual_size;
-	int i;
-	int got_new_line = 0;
-	float draw_x, draw_y;
-	float cursor_x, cursor_y;
-	const char *end;
-
-	float size = cursor->font_size;
-
-	/* to correct coords, convert to screen coords, round, and convert back */
-	Graphics()->GetScreen(&screen_x0, &screen_y0, &screen_x1, &screen_y1);
-	
-	fake_to_screen_x = (Graphics()->ScreenWidth()/(screen_x1-screen_x0));
-	fake_to_screen_y = (Graphics()->ScreenHeight()/(screen_y1-screen_y0));
-	actual_x = cursor->x * fake_to_screen_x;
-	actual_y = cursor->y * fake_to_screen_y;
-
-	cursor_x = actual_x / fake_to_screen_x;
-	cursor_y = actual_y / fake_to_screen_y;
-
-	/* same with size */
-	actual_size = size * fake_to_screen_y;
-	size = actual_size / fake_to_screen_y;
-
-	/* fetch font data */
-	if(!font)
-		font = default_font;
-	
-	if(!font)
-		return;
-
-	sizedata = font_get_size(font, actual_size);
-	font_render_setup(font, actual_size);
-	
-	/* set length */
-	if(length < 0)
-		length = strlen(text);
-		
-	end = text + length;
-
-	/* if we don't want to render, we can just skip the first outline pass */
-	i = 1;
-	if(cursor->flags&TEXTFLAG_RENDER)
-		i = 0;
-
-	for(;i < 2; i++)
-	{
-		const char *current = (char *)text;
-		const char *end = current+length;
-		draw_x = cursor_x;
-		draw_y = cursor_y;
-
-		if(cursor->flags&TEXTFLAG_RENDER)
-		{
-			// TODO: Make this better
-			glEnable(GL_TEXTURE_2D);
-			if (i == 0)
-				glBindTexture(GL_TEXTURE_2D, sizedata->textures[1]);
-			else
-				glBindTexture(GL_TEXTURE_2D, sizedata->textures[0]);
-
-			Graphics()->QuadsBegin();
-			if (i == 0)
-				Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*text_a);
-			else
-				Graphics()->SetColor(text_r, text_g, text_b, text_a);
-		}
-
-		while(current < end)
-		{
-			int new_line = 0;
-			const char *batch_end = end;
-			if(cursor->line_width > 0 && !(cursor->flags&TEXTFLAG_STOP_AT_END))
-			{
-				int wlen = word_length((char *)current);
-				TEXT_CURSOR compare = *cursor;
-				compare.x = draw_x;
-				compare.y = draw_y;
-				compare.flags &= ~TEXTFLAG_RENDER;
-				compare.line_width = -1;
-				gfx_text_ex(&compare, text, wlen);
-				
-				if(compare.x-draw_x > cursor->line_width)
-				{
-					/* word can't be fitted in one line, cut it */
-					TEXT_CURSOR cutter = *cursor;
-					cutter.charcount = 0;
-					cutter.x = draw_x;
-					cutter.y = draw_y;
-					cutter.flags &= ~TEXTFLAG_RENDER;
-					cutter.flags |= TEXTFLAG_STOP_AT_END;
-					
-					gfx_text_ex(&cutter, (const char *)current, wlen);
-					wlen = cutter.charcount;
-					new_line = 1;
-					
-					if(wlen <= 3) /* if we can't place 3 chars of the word on this line, take the next */
-						wlen = 0;
-				}
-				else if(compare.x-cursor->start_x > cursor->line_width)
-				{
-					new_line = 1;
-					wlen = 0;
-				}
-				
-				batch_end = current + wlen;
-			}
-			
-			while(current < batch_end)
-			{
-				const char *tmp;
-				float advance = 0;
-				int character = 0;
-				int nextcharacter = 0;
-				FONTCHAR *chr;
-
-				// TODO: UTF-8 decode
-				character = str_utf8_decode(&current);
-				tmp = current;
-				nextcharacter = str_utf8_decode(&tmp);
-				
-				if(character == '\n')
-				{
-					draw_x = cursor->start_x;
-					draw_y += size;
-					draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */
-					draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y;
-					continue;
-				}
-
-				chr = font_get_char(font, sizedata, character);
-
-				if(chr)
-				{
-					if(cursor->flags&TEXTFLAG_RENDER)
-					{
-						Graphics()->QuadsSetSubset(chr->uvs[0], chr->uvs[1], chr->uvs[2], chr->uvs[3]);
-						Graphics()->QuadsDrawTL(draw_x+chr->offset_x*size, draw_y+chr->offset_y*size, chr->width*size, chr->height*size);
-					}
-
-					advance = chr->advance_x + font_kerning(font, character, nextcharacter)/size;
-				}
-								
-				if(cursor->flags&TEXTFLAG_STOP_AT_END && draw_x+advance*size-cursor->start_x > cursor->line_width)
-				{
-					/* we hit the end of the line, no more to render or count */
-					current = end;
-					break;
-				}
-
-				draw_x += advance*size;
-				cursor->charcount++;
-			}
-			
-			if(new_line)
-			{
-				draw_x = cursor->start_x;
-				draw_y += size;
-				got_new_line = 1;
-				draw_x = (int)(draw_x * fake_to_screen_x) / fake_to_screen_x; /* realign */
-				draw_y = (int)(draw_y * fake_to_screen_y) / fake_to_screen_y;				
-			}
-		}
-
-		if(cursor->flags&TEXTFLAG_RENDER)
-			Graphics()->QuadsEnd();
-	}
-
-	cursor->x = draw_x;
-	
-	if(got_new_line)
-		cursor->y = draw_y;
-}
diff --git a/src/engine/client/ec_inp.cpp b/src/engine/client/ec_inp.cpp
deleted file mode 100644
index cf956471..00000000
--- a/src/engine/client/ec_inp.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
-#include <string.h>
-#include "SDL.h"
-
-#include <base/system.h>
-#include <engine/e_client_interface.h>
-#include <engine/e_config.h>
-#include <engine/client/graphics.h>
-
-static struct
-{
-	unsigned char presses;
-	unsigned char releases;
-} input_count[2][1024] = {{{0}}, {{0}}};
-
-static unsigned char input_state[2][1024] = {{0}, {0}};
-
-static int input_current = 0;
-static int input_grabbed = 0;
-
-static unsigned int last_release = 0;
-static unsigned int release_delta = -1;
-
-// TODO: Refactor: Remove this
-extern IEngineGraphics *Graphics();
-
-void inp_mouse_relative(int *x, int *y)
-{
-	int nx = 0, ny = 0;
-	float sens = config.inp_mousesens/100.0f;
-	
-	if(config.inp_grab)
-		SDL_GetRelativeMouseState(&nx, &ny);
-	else
-	{
-		if(input_grabbed)
-		{
-			SDL_GetMouseState(&nx,&ny);
-			SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2);
-			nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2;
-		}
-	}
-
-	*x = nx*sens;
-	*y = ny*sens;
-}
-
-enum
-{
-	INPUT_BUFFER_SIZE=32
-};
-
-static INPUT_EVENT input_events[INPUT_BUFFER_SIZE];
-static int num_events = 0;
-
-static void add_event(int unicode, int key, int flags)
-{
-	if(num_events != INPUT_BUFFER_SIZE)
-	{
-		input_events[num_events].unicode = unicode;
-		input_events[num_events].key = key;
-		input_events[num_events].flags = flags;
-		num_events++;
-	}
-}
-
-int inp_num_events()
-{
-	return num_events;
-}
-
-void inp_clear_events()
-{
-	num_events = 0;
-}
-
-INPUT_EVENT inp_get_event(int index)
-{
-	if(index < 0 || index >= num_events)
-	{
-		INPUT_EVENT e = {0,0};
-		return e;
-	}
-	
-	return input_events[index];
-}
-
-void inp_init()
-{
-	SDL_EnableUNICODE(1);
-	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); 
-}
-
-void inp_mouse_mode_absolute()
-{
-	SDL_ShowCursor(1);
-	input_grabbed = 0;
-	if(config.inp_grab)
-		SDL_WM_GrabInput(SDL_GRAB_OFF);
-}
-
-void inp_mouse_mode_relative()
-{
-	SDL_ShowCursor(0);
-	input_grabbed = 1;
-	if(config.inp_grab)
-		SDL_WM_GrabInput(SDL_GRAB_ON);
-}
-
-int inp_mouse_doubleclick()
-{
-	return release_delta < (time_freq() >> 2);
-}
-
-void inp_clear_key_states()
-{
-	mem_zero(input_state, sizeof(input_state));
-	mem_zero(input_count, sizeof(input_count));
-}
-
-int inp_key_presses(int key)
-{
-	return input_count[input_current][key].presses;
-}
-
-int inp_key_releases(int key)
-{
-	return input_count[input_current][key].releases;
-}
-
-int inp_key_state(int key)
-{
-	return input_state[input_current][key];
-}
-
-int inp_key_pressed(int key) { return input_state[input_current][key]; }
-int inp_key_was_pressed(int key) { return input_state[input_current^1][key]; }
-int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); }
-int inp_button_pressed(int button) { return input_state[input_current][button]; }
-
-void inp_update()
-{
-	int i;
-	
-	if(input_grabbed && !Graphics()->WindowActive())
-		inp_mouse_mode_absolute();
-
-	/*if(!input_grabbed && Graphics()->WindowActive())
-		inp_mouse_mode_relative();*/
-	
-	/* clear and begin count on the other one */
-	input_current^=1;
-	mem_zero(&input_count[input_current], sizeof(input_count[input_current]));
-	mem_zero(&input_state[input_current], sizeof(input_state[input_current]));
-	
-	{
-		Uint8 *state = SDL_GetKeyState(&i);
-		if(i >= KEY_LAST)
-			i = KEY_LAST-1;
-		mem_copy(input_state[input_current], state, i);
-	}
-	
-	/* these states must always be updated manually because they are not in the GetKeyState from SDL */
-	i = SDL_GetMouseState(NULL, NULL);
-	if(i&SDL_BUTTON(1)) input_state[input_current][KEY_MOUSE_1] = 1; /* 1 is left */ 
-	if(i&SDL_BUTTON(3)) input_state[input_current][KEY_MOUSE_2] = 1; /* 3 is right */ 
-	if(i&SDL_BUTTON(2)) input_state[input_current][KEY_MOUSE_3] = 1; /* 2 is middle */ 
-	if(i&SDL_BUTTON(4)) input_state[input_current][KEY_MOUSE_4] = 1; 
-	if(i&SDL_BUTTON(5)) input_state[input_current][KEY_MOUSE_5] = 1; 
-	if(i&SDL_BUTTON(6)) input_state[input_current][KEY_MOUSE_6] = 1; 
-	if(i&SDL_BUTTON(7)) input_state[input_current][KEY_MOUSE_7] = 1; 
-	if(i&SDL_BUTTON(8)) input_state[input_current][KEY_MOUSE_8] = 1; 	
-	
-	{
-		SDL_Event event;
-	
-		while(SDL_PollEvent(&event))
-		{
-			int key = -1;
-			int action = INPFLAG_PRESS;
-			switch (event.type)
-			{
-				/* handle keys */
-				case SDL_KEYDOWN:
-					/*if(event.key.keysym.unicode < 255) */
-					add_event(event.key.keysym.unicode, 0, 0);
-					key = event.key.keysym.sym;
-					break;
-				case SDL_KEYUP:
-					action = INPFLAG_RELEASE;
-					key = event.key.keysym.sym;
-					break;
-				
-				/* handle mouse buttons */
-				case SDL_MOUSEBUTTONUP:
-					action = INPFLAG_RELEASE;
-					
-					if(event.button.button == 1)
-					{
-						release_delta = time_get() - last_release;
-						last_release = time_get();
-					}
-					
-					/* fall through */
-				case SDL_MOUSEBUTTONDOWN:
-					if(event.button.button == SDL_BUTTON_LEFT) key = KEY_MOUSE_1;
-					if(event.button.button == SDL_BUTTON_RIGHT) key = KEY_MOUSE_2;
-					if(event.button.button == SDL_BUTTON_MIDDLE) key = KEY_MOUSE_3;
-					if(event.button.button == SDL_BUTTON_WHEELUP) key = KEY_MOUSE_WHEEL_UP;
-					if(event.button.button == SDL_BUTTON_WHEELDOWN) key = KEY_MOUSE_WHEEL_DOWN;
-					if(event.button.button == 6) key = KEY_MOUSE_6;
-					if(event.button.button == 7) key = KEY_MOUSE_7;
-					if(event.button.button == 8) key = KEY_MOUSE_8;
-					break;
-					
-				/* other messages */
-				case SDL_QUIT:
-					/* TODO: cleaner exit */
-					exit(0);
-					break;
-			}
-			
-			/* */
-			if(key != -1)
-			{
-				input_count[input_current][key].presses++;
-				if(action == INPFLAG_PRESS)
-					input_state[input_current][key] = 1;
-				add_event(0, key, action);
-			}
-
-		}
-	}
-}
diff --git a/src/engine/client/ec_snd.cpp b/src/engine/client/ec_snd.cpp
deleted file mode 100644
index 3baea982..00000000
--- a/src/engine/client/ec_snd.cpp
+++ /dev/null
@@ -1,471 +0,0 @@
-/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
-#include <base/system.h>
-#include <engine/e_client_interface.h>
-#include <engine/client/graphics.h>
-#include <engine/e_config.h>
-#include <engine/e_engine.h>
-
-#include "SDL.h"
-
-extern "C" { // wavpack
-	#include <engine/external/wavpack/wavpack.h>
-}
-#include <stdio.h>
-#include <stdlib.h>
-#include <math.h>
-
-enum
-{
-	NUM_SAMPLES = 512,
-	NUM_VOICES = 64,
-	NUM_CHANNELS = 16,
-	
-	MAX_FRAMES = 1024
-};
-
-typedef struct
-{
-	short *data;
-	int num_frames;
-	int rate;
-	int channels;
-	int loop_start;
-	int loop_end;
-} SAMPLE;
-
-typedef struct
-{
-	int vol;
-	int pan;
-} CHANNEL;
-
-typedef struct
-{
-	SAMPLE *snd;
-	CHANNEL *channel;
-	int tick;
-	int vol; /* 0 - 255 */
-	int flags;
-	int x, y;
-} VOICE;
-
-static SAMPLE samples[NUM_SAMPLES] = { {0} };
-static VOICE voices[NUM_VOICES] = { {0} };
-static CHANNEL channels[NUM_CHANNELS] = { {255, 0} };
-
-static LOCK sound_lock = 0;
-static int sound_enabled = 0;
-
-static int center_x = 0;
-static int center_y = 0;
-
-static int mixing_rate = 48000;
-static volatile int sound_volume = 100;
-
-static int next_voice = 0;
-
-void snd_set_channel(int cid, float vol, float pan)
-{
-	channels[cid].vol = (int)(vol*255.0f);
-	channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */
-}
-
-static int play(int cid, int sid, int flags, float x, float y)
-{
-	int vid = -1;
-	int i;
-	
-	lock_wait(sound_lock);
-	
-	/* search for voice */
-	for(i = 0; i < NUM_VOICES; i++)
-	{
-		int id = (next_voice + i) % NUM_VOICES;
-		if(!voices[id].snd)
-		{
-			vid = id;
-			next_voice = id+1;
-			break;
-		}
-	}
-	
-	/* voice found, use it */
-	if(vid != -1)
-	{
-		voices[vid].snd = &samples[sid];
-		voices[vid].channel = &channels[cid];
-		voices[vid].tick = 0;
-		voices[vid].vol = 255;
-		voices[vid].flags = flags;
-		voices[vid].x = (int)x;
-		voices[vid].y = (int)y;
-	}
-	
-	lock_release(sound_lock);
-	return vid;
-}
-
-int snd_play_at(int cid, int sid, int flags, float x, float y)
-{
-	return play(cid, sid, flags|SNDFLAG_POS, x, y);
-}
-
-int snd_play(int cid, int sid, int flags)
-{
-	return play(cid, sid, flags, 0, 0);
-}
-
-void snd_stop(int vid)
-{
-	/* TODO: a nice fade out */
-	lock_wait(sound_lock);
-	voices[vid].snd = 0;
-	lock_release(sound_lock);
-}
-
-/* TODO: there should be a faster way todo this */
-static short int2short(int i)
-{
-	if(i > 0x7fff)
-		return 0x7fff;
-	else if(i < -0x7fff)
-		return -0x7fff;
-	return i;
-}
-
-static int iabs(int i)
-{
-	if(i<0)
-		return -i;
-	return i;
-}
-
-static void mix(short *final_out, unsigned frames)
-{
-	int mix_buffer[MAX_FRAMES*2] = {0};
-	int master_vol;
-
-	/* aquire lock while we are mixing */
-	lock_wait(sound_lock);
-	
-	master_vol = sound_volume;
-	
-	for(unsigned i = 0; i < NUM_VOICES; i++)
-	{
-		if(voices[i].snd)
-		{
-			/* mix voice */
-			VOICE *v = &voices[i];
-			int *out = mix_buffer;
-
-			int step = v->snd->channels; /* setup input sources */
-			short *in_l = &v->snd->data[v->tick*step];
-			short *in_r = &v->snd->data[v->tick*step+1];
-			
-			unsigned end = v->snd->num_frames-v->tick;
-
-			int rvol = v->channel->vol;
-			int lvol = v->channel->vol;
-
-			/* make sure that we don't go outside the sound data */
-			if(frames < end)
-				end = frames;
-			
-			/* check if we have a mono sound */
-			if(v->snd->channels == 1)
-				in_r = in_l;
-
-			/* volume calculation */
-			if(v->flags&SNDFLAG_POS && v->channel->pan)
-			{
-				/* TODO: we should respect the channel panning value */
-				const int range = 1500; /* magic value, remove */
-				int dx = v->x - center_x;
-				int dy = v->y - center_y;
-				int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */
-				int p = iabs(dx);
-				if(dist < range)
-				{
-					/* panning */
-					if(dx > 0)
-						lvol = ((range-p)*lvol)/range;
-					else
-						rvol = ((range-p)*rvol)/range;
-					
-					/* falloff */
-					lvol = (lvol*(range-dist))/range;
-					rvol = (rvol*(range-dist))/range;
-				}
-				else
-				{
-					lvol = 0;
-					rvol = 0;
-				}
-			}
-
-			/* process all frames */
-			for(unsigned s = 0; s < end; s++)
-			{
-				*out++ += (*in_l)*lvol;
-				*out++ += (*in_r)*rvol;
-				in_l += step;
-				in_r += step;
-				v->tick++;
-			}
-			
-			/* free voice if not used any more */
-			if(v->tick == v->snd->num_frames)
-				v->snd = 0;
-			
-		}
-	}
-	
-	
-	/* release the lock */
-	lock_release(sound_lock);
-
-	{
-		/* clamp accumulated values */
-		/* TODO: this seams slow */
-		for(unsigned i = 0; i < frames; i++)
-		{
-			int j = i<<1;
-			int vl = ((mix_buffer[j]*master_vol)/101)>>8;
-			int vr = ((mix_buffer[j+1]*master_vol)/101)>>8;
-
-			final_out[j] = int2short(vl);
-			final_out[j+1] = int2short(vr);
-		}
-	}
-
-#if defined(CONF_ARCH_ENDIAN_BIG)
-	swap_endian(final_out, sizeof(short), frames * 2);
-#endif
-}
-
-static void sdlcallback(void *unused, Uint8 *stream, int len)
-{
-	mix((short *)stream, len/2/2);
-}
-
-int snd_init()
-{
-    SDL_AudioSpec format;
-	
-	sound_lock = lock_create();
-	
-	if(!config.snd_enable)
-		return 0;
-	
-	mixing_rate = config.snd_rate;
-
-    /* Set 16-bit stereo audio at 22Khz */
-    format.freq = config.snd_rate;
-    format.format = AUDIO_S16;
-    format.channels = 2;
-    format.samples = config.snd_buffer_size;
-    format.callback = sdlcallback;
-    format.userdata = NULL;
-
-    /* Open the audio device and start playing sound! */
-    if(SDL_OpenAudio(&format, NULL) < 0)
-	{
-        dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError());
-		return -1;
-    }
-	else
-        dbg_msg("client/sound", "sound init successful");
-
-	SDL_PauseAudio(0);
-	
-	sound_enabled = 1;
-	snd_update(); /* update the volume */
-	return 0;
-}
-
-// TODO: Refactor: Remove this
-extern IEngineGraphics *Graphics();
-
-
-int snd_update()
-{
-	/* update volume */
-	int wanted_volume = config.snd_volume;
-	
-	if(!Graphics()->WindowActive() && config.snd_nonactive_mute)
-		wanted_volume = 0;
-	
-	if(wanted_volume != sound_volume)
-	{
-		lock_wait(sound_lock);
-		sound_volume = wanted_volume;
-		lock_release(sound_lock);
-	}
-	
-	return 0;
-}
-
-int snd_shutdown()
-{
-	SDL_CloseAudio();
-	lock_destroy(sound_lock);
-	return 0;
-}
-
-int snd_alloc_id()
-{
-	/* TODO: linear search, get rid of it */
-	unsigned sid;
-	for(sid = 0; sid < NUM_SAMPLES; sid++)
-	{
-		if(samples[sid].data == 0x0)
-			return sid;
-	}
-
-	return -1;
-}
-
-static void rate_convert(int sid)
-{
-	SAMPLE *snd = &samples[sid];
-	int num_frames = 0;
-	short *new_data = 0;
-	int i;
-	
-	/* make sure that we need to convert this sound */
-	if(!snd->data || snd->rate == mixing_rate)
-		return;
-
-	/* allocate new data */
-	num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate);
-	new_data = (short *)mem_alloc(num_frames*snd->channels*sizeof(short), 1);
-	
-	for(i = 0; i < num_frames; i++)
-	{
-		/* resample TODO: this should be done better, like linear atleast */
-		float a = i/(float)num_frames;
-		int f = (int)(a*snd->num_frames);
-		if(f >= snd->num_frames)
-			f = snd->num_frames-1;
-		
-		/* set new data */
-		if(snd->channels == 1)
-			new_data[i] = snd->data[f];
-		else if(snd->channels == 2)
-		{
-			new_data[i*2] = snd->data[f*2];
-			new_data[i*2+1] = snd->data[f*2+1];
-		}
-	}
-	
-	/* free old data and apply new */
-	mem_free(snd->data);
-	snd->data = new_data;
-	snd->num_frames = num_frames;
-}
-
-
-static IOHANDLE file = NULL;
-
-static int read_data(void *buffer, int size)
-{
-	return io_read(file, buffer, size);
-}
-
-int snd_load_wv(const char *filename)
-{
-	SAMPLE *snd;
-	int sid = -1;
-	char error[100];
-	WavpackContext *context;
-	
-	/* don't waste memory on sound when we are stress testing */
-	if(config.dbg_stress)
-		return -1;
-		
-	/* no need to load sound when we are running with no sound */
-	if(!sound_enabled)
-		return 1;
-
-	file = engine_openfile(filename, IOFLAG_READ); /* TODO: use system.h stuff for this */
-	if(!file)
-	{
-		dbg_msg("sound/wv", "failed to open %s", filename);
-		return -1;
-	}
-
-	sid = snd_alloc_id();
-	if(sid < 0)
-		return -1;
-	snd = &samples[sid];
-
-	context = WavpackOpenFileInput(read_data, error);
-	if (context)
-	{
-		int samples = WavpackGetNumSamples(context);
-		int bitspersample = WavpackGetBitsPerSample(context);
-		unsigned int samplerate = WavpackGetSampleRate(context);
-		int channels = WavpackGetNumChannels(context);
-		int *data;
-		int *src;
-		short *dst;
-		int i;
-
-		snd->channels = channels;
-		snd->rate = samplerate;
-
-		if(snd->channels > 2)
-		{
-			dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename);
-			return -1;
-		}
-
-		/*
-		if(snd->rate != 44100)
-		{
-			dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
-			return -1;
-		}*/
-		
-		if(bitspersample != 16)
-		{
-			dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename);
-			return -1;
-		}
-
-		data = (int *)mem_alloc(4*samples*channels, 1);
-		WavpackUnpackSamples(context, data, samples); /* TODO: check return value */
-		src = data;
-		
-		snd->data = (short *)mem_alloc(2*samples*channels, 1);
-		dst = snd->data;
-
-		for (i = 0; i < samples*channels; i++)
-			*dst++ = (short)*src++;
-
-		mem_free(data);
-
-		snd->num_frames = samples;
-		snd->loop_start = -1;
-		snd->loop_end = -1;
-	}
-	else
-	{
-		dbg_msg("sound/wv", "failed to open %s: %s", filename, error);
-	}
-
-	io_close(file);
-	file = NULL;
-
-	if(config.debug)
-		dbg_msg("sound/wv", "loaded %s", filename);
-
-	rate_convert(sid);
-	return sid;
-}
-
-void snd_set_listener_pos(float x, float y)
-{
-	center_x = (int)x;
-	center_y = (int)y;
-}
diff --git a/src/engine/client/ec_srvbrowse.cpp b/src/engine/client/ec_srvbrowse.cpp
deleted file mode 100644
index 1b04937a..00000000
--- a/src/engine/client/ec_srvbrowse.cpp
+++ /dev/null
@@ -1,736 +0,0 @@
-/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
-#include <base/system.h>
-#include <engine/e_network.h>
-#include <engine/e_client_interface.h>
-#include <engine/e_config.h>
-#include <engine/e_memheap.h>
-#include <engine/e_engine.h>
-
-#include <mastersrv/mastersrv.h>
-
-#include <string.h>
-#include <stdlib.h>
-
-extern CNetClient m_NetClient;
-
-
-/* ------ server browse ---- */
-/* TODO: move all this to a separate file */
-
-typedef struct SERVERENTRY_t SERVERENTRY;
-struct SERVERENTRY_t
-{
-	NETADDR addr;
-	int64 request_time;
-	int got_info;
-	SERVER_INFO info;
-	
-	SERVERENTRY *next_ip; /* ip hashed list */
-	
-	SERVERENTRY *prev_req; /* request list */
-	SERVERENTRY *next_req;
-};
-
-static HEAP *serverlist_heap = 0;
-static SERVERENTRY **serverlist = 0;
-static int *sorted_serverlist = 0;
-
-enum
-{
-	MAX_FAVORITES=256
-};
-
-static NETADDR favorite_servers[MAX_FAVORITES];
-static int num_favorite_servers = 0;
-
-static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */
-
-static SERVERENTRY *first_req_server = 0; /* request list */
-static SERVERENTRY *last_req_server = 0;
-static int num_requests = 0;
-
-static int need_refresh = 0;
-
-static int num_sorted_servers = 0;
-static int num_sorted_servers_capacity = 0;
-static int num_servers = 0;
-static int num_server_capacity = 0;
-
-static int sorthash = 0;
-static char filterstring[64] = {0};
-static char filtergametypestring[128] = {0};
-
-/* the token is to keep server refresh separated from each other */
-static int current_token = 1;
-
-static int serverlist_type = 0;
-static int64 broadcast_time = 0;
-
-int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; }
-int client_serverbrowse_num() { return num_servers; }
-
-SERVER_INFO *client_serverbrowse_get(int index)
-{
-	if(index < 0 || index >= num_servers)
-		return 0;
-	return &serverlist[index]->info;
-}
-
-int client_serverbrowse_sorted_num() { return num_sorted_servers; }
-
-SERVER_INFO *client_serverbrowse_sorted_get(int index)
-{
-	if(index < 0 || index >= num_sorted_servers)
-		return 0;
-	return &serverlist[sorted_serverlist[index]]->info;
-}
-
-
-int client_serverbrowse_num_requests()
-{
-	return num_requests;
-}
-
-static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	return strcmp(a->info.name, b->info.name);
-}
-
-static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	return strcmp(a->info.map, b->info.map);
-}
-
-static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	if(a->info.latency > b->info.latency) return 1;
-	if(a->info.latency < b->info.latency) return -1;
-	return 0;
-}
-
-static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	return strcmp(a->info.gametype, b->info.gametype);
-}
-
-static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	if(a->info.progression > b->info.progression) return 1;
-	if(a->info.progression < b->info.progression) return -1;
-	return 0;
-}
-
-static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi)
-{
-	SERVERENTRY *a = serverlist[*(const int*)ai];
-	SERVERENTRY *b = serverlist[*(const int*)bi];
-	if(a->info.num_players > b->info.num_players) return 1;
-	if(a->info.num_players < b->info.num_players) return -1;
-	return 0;
-}
-
-static void client_serverbrowse_filter()
-{
-	int i = 0, p = 0;
-	num_sorted_servers = 0;
-
-	/* allocate the sorted list */	
-	if(num_sorted_servers_capacity < num_servers)
-	{
-		if(sorted_serverlist)
-			mem_free(sorted_serverlist);
-		num_sorted_servers_capacity = num_servers;
-		sorted_serverlist = (int *)mem_alloc(num_sorted_servers_capacity*sizeof(int), 1);
-	}
-	
-	/* filter the servers */
-	for(i = 0; i < num_servers; i++)
-	{
-		int filtered = 0;
-
-		if(config.b_filter_empty && serverlist[i]->info.num_players == 0)
-			filtered = 1;
-		else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players)
-			filtered = 1;
-		else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD)
-			filtered = 1;
-		else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0))
-			filtered = 1;
-		else if(config.b_filter_pure_map &&
-			!(strcmp(serverlist[i]->info.map, "dm1") == 0 ||
-			strcmp(serverlist[i]->info.map, "dm2") == 0 ||
-			strcmp(serverlist[i]->info.map, "dm6") == 0 ||
-			strcmp(serverlist[i]->info.map, "dm7") == 0 ||
-			strcmp(serverlist[i]->info.map, "dm8") == 0 ||
-			strcmp(serverlist[i]->info.map, "dm9") == 0 ||
-			strcmp(serverlist[i]->info.map, "ctf1") == 0 ||
-			strcmp(serverlist[i]->info.map, "ctf2") == 0 ||
-			strcmp(serverlist[i]->info.map, "ctf3") == 0 ||
-			strcmp(serverlist[i]->info.map, "ctf4") == 0 ||
-			strcmp(serverlist[i]->info.map, "ctf5") == 0)
-		)
-		{
-			filtered = 1;
-		}
-		else if(config.b_filter_ping < serverlist[i]->info.latency)
-			filtered = 1;
-		else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0)
-			filtered = 1;
-		else 
-		{
-			if(config.b_filter_string[0] != 0)
-			{
-				int matchfound = 0;
-				
-				serverlist[i]->info.quicksearch_hit = 0;
-
-				/* match against server name */
-				if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string))
-				{
-					matchfound = 1;
-					serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME;
-				}
-
-				/* match against players */				
-				for(p = 0; p < serverlist[i]->info.num_players; p++)
-				{
-					if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string))
-					{
-						matchfound = 1;
-						serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME;
-						break;
-					}
-				}
-				
-				/* match against map */
-				if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string))
-				{
-					matchfound = 1;
-					serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME;
-				}
-				
-				if(!matchfound)
-					filtered = 1;
-			}
-			
-			if(!filtered && config.b_filter_gametype[0] != 0)
-			{
-				/* match against game type */
-				if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype))
-					filtered = 1;
-			}
-		}
-
-		if(filtered == 0)
-			sorted_serverlist[num_sorted_servers++] = i;
-	}
-}
-
-static int client_serverbrowse_sorthash()
-{
-	int i = config.b_sort&0xf;
-	i |= config.b_filter_empty<<4;
-	i |= config.b_filter_full<<5;
-	i |= config.b_filter_pw<<6;
-	i |= config.b_sort_order<<7;
-	i |= config.b_filter_compatversion<<8;
-	i |= config.b_filter_pure<<9;
-	i |= config.b_filter_pure_map<<10;
-	i |= config.b_filter_ping<<16;
-	return i;
-}
-
-static void client_serverbrowse_sort()
-{
-	int i;
-	
-	/* create filtered list */
-	client_serverbrowse_filter();
-	
-	/* sort */
-	if(config.b_sort == BROWSESORT_NAME)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name);
-	else if(config.b_sort == BROWSESORT_PING)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping);
-	else if(config.b_sort == BROWSESORT_MAP)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map);
-	else if(config.b_sort == BROWSESORT_NUMPLAYERS)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers);
-	else if(config.b_sort == BROWSESORT_GAMETYPE)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype);
-	else if(config.b_sort == BROWSESORT_PROGRESSION)
-		qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression);
-	
-	/* invert the list if requested */
-	if(config.b_sort_order)
-	{
-		for(i = 0; i < num_sorted_servers/2; i++)
-		{
-			int temp = sorted_serverlist[i];
-			sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1];
-			sorted_serverlist[num_sorted_servers-i-1] = temp;
-		}
-	}
-	
-	/* set indexes */
-	for(i = 0; i < num_sorted_servers; i++)
-		serverlist[sorted_serverlist[i]]->info.sorted_index = i;
-	
-	str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring)); 
-	str_copy(filterstring, config.b_filter_string, sizeof(filterstring)); 
-	sorthash = client_serverbrowse_sorthash();
-}
-
-static void client_serverbrowse_remove_request(SERVERENTRY *entry)
-{
-	if(entry->prev_req || entry->next_req || first_req_server == entry)
-	{
-		if(entry->prev_req)
-			entry->prev_req->next_req = entry->next_req;
-		else
-			first_req_server = entry->next_req;
-			
-		if(entry->next_req)
-			entry->next_req->prev_req = entry->prev_req;
-		else
-			last_req_server = entry->prev_req;
-			
-		entry->prev_req = 0;
-		entry->next_req = 0;
-		num_requests--;
-	}
-}
-
-static SERVERENTRY *client_serverbrowse_find(NETADDR *addr)
-{
-	SERVERENTRY *entry = serverlist_ip[addr->ip[0]];
-	
-	for(; entry; entry = entry->next_ip)
-	{
-		if(net_addr_comp(&entry->addr, addr) == 0)
-			return entry;
-	}
-	return (SERVERENTRY*)0;
-}
-
-void client_serverbrowse_queuerequest(SERVERENTRY *entry)
-{
-	/* add it to the list of servers that we should request info from */
-	entry->prev_req = last_req_server;
-	if(last_req_server)
-		last_req_server->next_req = entry;
-	else
-		first_req_server = entry;
-	last_req_server = entry;
-	
-	num_requests++;
-}
-
-void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info)
-{
-	int fav = entry->info.favorite;
-	entry->info = *info;
-	entry->info.favorite = fav;
-	entry->info.netaddr = entry->addr;
-	
-	// all these are just for nice compability
-	if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0)
-		str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype));
-	else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0)
-		str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype));
-	else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0)
-		str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype));
-
-	/*if(!request)
-	{
-		entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
-		client_serverbrowse_remove_request(entry);
-	}*/
-	
-	entry->got_info = 1;
-	client_serverbrowse_sort();	
-}
-
-SERVERENTRY *client_serverbrowse_add(NETADDR *addr)
-{
-	int hash = addr->ip[0];
-	SERVERENTRY *entry = 0;
-	int i;
-
-	/* create new entry */
-	entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY));
-	mem_zero(entry, sizeof(SERVERENTRY));
-	
-	/* set the info */
-	entry->addr = *addr;
-	entry->info.netaddr = *addr;
-	
-	entry->info.latency = 999;
-	str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d",
-		addr->ip[0], addr->ip[1], addr->ip[2],
-		addr->ip[3], addr->port);
-	str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */
-		addr->ip[0], addr->ip[1], addr->ip[2],
-		addr->ip[3], addr->port);	
-	
-	/*if(serverlist_type == BROWSETYPE_LAN)
-		entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/
-
-	/* check if it's a favorite */
-	for(i = 0; i < num_favorite_servers; i++)
-	{
-		if(net_addr_comp(addr, &favorite_servers[i]) == 0)
-			entry->info.favorite = 1;
-	}
-	
-	/* add to the hash list */	
-	entry->next_ip = serverlist_ip[hash];
-	serverlist_ip[hash] = entry;
-	
-	if(num_servers == num_server_capacity)
-	{
-		SERVERENTRY **newlist;
-		num_server_capacity += 100;
-		newlist = (SERVERENTRY **)mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1);
-		mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*));
-		mem_free(serverlist);
-		serverlist = newlist;
-	}
-	
-	/* add to list */
-	serverlist[num_servers] = entry;
-	entry->info.server_index = num_servers;
-	num_servers++;
-	
-	return entry;
-}
-
-void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info)
-{
-	SERVERENTRY *entry = 0;
-	if(type == BROWSESET_MASTER_ADD)
-	{
-		if(serverlist_type != BROWSETYPE_INTERNET)
-			return;
-			
-		if(!client_serverbrowse_find(addr))
-		{
-			entry = client_serverbrowse_add(addr);
-			client_serverbrowse_queuerequest(entry);
-		}
-	}
-	else if(type == BROWSESET_FAV_ADD)
-	{
-		if(serverlist_type != BROWSETYPE_FAVORITES)
-			return;
-			
-		if(!client_serverbrowse_find(addr))
-		{
-			entry = client_serverbrowse_add(addr);
-			client_serverbrowse_queuerequest(entry);
-		}
-	}
-	else if(type == BROWSESET_TOKEN)
-	{
-		if(token != current_token)
-			return;
-			
-		entry = client_serverbrowse_find(addr);
-		if(!entry)
-			entry = client_serverbrowse_add(addr);
-		if(entry)
-		{
-			client_serverbrowse_setinfo(entry, info);
-			if(serverlist_type == BROWSETYPE_LAN)
-				entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();
-			else
-				entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
-			client_serverbrowse_remove_request(entry);				
-		}
-	}
-	else if(type == BROWSESET_OLD_INTERNET)
-	{
-		entry = client_serverbrowse_find(addr);
-		if(entry)
-		{
-			client_serverbrowse_setinfo(entry, info);
-			
-			if(serverlist_type == BROWSETYPE_LAN)
-				entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();
-			else
-				entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
-			client_serverbrowse_remove_request(entry);				
-		}
-	}
-	else if(type == BROWSESET_OLD_LAN)
-	{
-		entry = client_serverbrowse_find(addr);
-		if(entry)
-		if(!entry)
-			entry = client_serverbrowse_add(addr);
-		if(entry)
-			client_serverbrowse_setinfo(entry, info);
-	}
-	
-	client_serverbrowse_sort();
-}
-
-void client_serverbrowse_refresh(int type)
-{
-	/* clear out everything */
-	if(serverlist_heap)
-		memheap_destroy(serverlist_heap);
-	serverlist_heap = memheap_create();
-	num_servers = 0;
-	num_sorted_servers = 0;
-	mem_zero(serverlist_ip, sizeof(serverlist_ip));
-	first_req_server = 0;
-	last_req_server = 0;
-	num_requests = 0;
-	
-	/* next token */
-	current_token = (current_token+1)&0xff;
-	
-	/* */
-	serverlist_type = type;
-
-	if(type == BROWSETYPE_LAN)
-	{
-		unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1];
-		CNetChunk Packet;
-		int i;
-		
-		mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
-		Buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;
-			
-		Packet.m_ClientID = -1;
-		mem_zero(&Packet, sizeof(Packet));
-		Packet.m_Address.ip[0] = 255;
-		Packet.m_Address.ip[1] = 255;
-		Packet.m_Address.ip[2] = 255;
-		Packet.m_Address.ip[3] = 255;
-		Packet.m_Flags = NETSENDFLAG_CONNLESS;
-		Packet.m_DataSize = sizeof(Buffer);
-		Packet.m_pData = Buffer;
-		broadcast_time = time_get();
-
-		for(i = 8303; i <= 8310; i++)
-		{
-			Packet.m_Address.port = i;
-			m_NetClient.Send(&Packet);
-		}
-
-		if(config.debug)
-			dbg_msg("client", "broadcasting for servers");
-	}
-	else if(type == BROWSETYPE_INTERNET)
-		need_refresh = 1;
-	else if(type == BROWSETYPE_FAVORITES)
-	{
-		for(int i = 0; i < num_favorite_servers; i++)
-			client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0);
-	}
-}
-
-static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry)
-{
-	/*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/
-	CNetChunk Packet;
-
-	if(config.debug)
-	{
-		dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
-			addr->ip[0], addr->ip[1], addr->ip[2],
-			addr->ip[3], addr->port);
-	}
-	
-	/*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
-	buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/
-	
-	Packet.m_ClientID = -1;
-	Packet.m_Address = *addr;
-	Packet.m_Flags = NETSENDFLAG_CONNLESS;
-	/*p.data_size = sizeof(buffer);
-	p.data = buffer;
-	netclient_send(net, &p);*/
-
-	/* send old requtest style aswell */	
-	Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO);
-	Packet.m_pData = SERVERBROWSE_OLD_GETINFO;
-	m_NetClient.Send(&Packet);
-	
-	if(entry)
-		entry->request_time = time_get();
-}
-
-void client_serverbrowse_request(NETADDR *addr)
-{
-	client_serverbrowse_request_impl(addr, 0);
-}
-
-
-void client_serverbrowse_update()
-{
-	int64 timeout = time_freq();
-	int64 now = time_get();
-	int count;
-	SERVERENTRY *entry, *next;
-	
-	/* do server list requests */
-	if(need_refresh && !mastersrv_refreshing())
-	{
-		NETADDR addr;
-		CNetChunk Packet;
-		int i;
-		
-		need_refresh = 0;
-		
-		mem_zero(&Packet, sizeof(Packet));
-		Packet.m_ClientID = -1;
-		Packet.m_Flags = NETSENDFLAG_CONNLESS;
-		Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST);
-		Packet.m_pData = SERVERBROWSE_GETLIST;
-		
-		for(i = 0; i < MAX_MASTERSERVERS; i++)
-		{
-			addr = mastersrv_get(i);
-			if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3])
-				continue;
-			
-			Packet.m_Address = addr;
-			m_NetClient.Send(&Packet);
-		}
-
-		if(config.debug)
-			dbg_msg("client", "requesting server list");
-	}
-	
-	/* do timeouts */
-	entry = first_req_server;
-	while(1)
-	{
-		if(!entry) /* no more entries */
-			break;
-			
-		next = entry->next_req;
-		
-		if(entry->request_time && entry->request_time+timeout < now)
-		{
-			/* timeout */
-			client_serverbrowse_remove_request(entry);
-			num_requests--;
-		}
-			
-		entry = next;
-	}
-
-	/* do timeouts */
-	entry = first_req_server;
-	count = 0;
-	while(1)
-	{
-		if(!entry) /* no more entries */
-			break;
-		
-		/* no more then 10 concurrent requests */
-		if(count == config.b_max_requests)
-			break;
-			
-		if(entry->request_time == 0)
-			client_serverbrowse_request_impl(&entry->addr, entry);
-		
-		count++;
-		entry = entry->next_req;
-	}
-	
-	/* check if we need to resort */
-	/* TODO: remove the strcmp */
-	if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0)
-		client_serverbrowse_sort();
-}
-
-
-int client_serverbrowse_isfavorite(NETADDR addr)
-{
-	/* search for the address */
-	int i;
-	for(i = 0; i < num_favorite_servers; i++)
-	{
-		if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
-			return 1;
-	}
-	return 0;
-}
-
-void client_serverbrowse_addfavorite(NETADDR addr)
-{
-	int i;
-	SERVERENTRY *entry;
-	
-	if(num_favorite_servers == MAX_FAVORITES)
-		return;
-
-	/* make sure that we don't already have the server in our list */
-	for(i = 0; i < num_favorite_servers; i++)
-	{
-		if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
-			return;
-	}
-	
-	/* add the server to the list */
-	favorite_servers[num_favorite_servers++] = addr;
-	entry = client_serverbrowse_find(&addr);
-	if(entry)
-		entry->info.favorite = 1;
-	dbg_msg("", "added fav, %p", entry);
-}
-
-void client_serverbrowse_removefavorite(NETADDR addr)
-{
-	int i;
-	SERVERENTRY *entry;
-	
-	for(i = 0; i < num_favorite_servers; i++)
-	{
-		if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
-		{
-			mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1));
-			num_favorite_servers--;
-
-			entry = client_serverbrowse_find(&addr);
-			if(entry)
-				entry->info.favorite = 0;
-
-			return;
-		}
-	}
-}
-
-void client_serverbrowse_save()
-{
-	int i;
-	char addrstr[128];
-	char buffer[256];
-	for(i = 0; i < num_favorite_servers; i++)
-	{
-		net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr));
-		str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr);
-		engine_config_write_line(buffer);
-	}
-}
-
-
-int client_serverbrowse_refreshingmasters()
-{
-	return mastersrv_refreshing();
-}
diff --git a/src/engine/client/editor.h b/src/engine/client/editor.h
deleted file mode 100644
index 336a46d4..00000000
--- a/src/engine/client/editor.h
+++ /dev/null
@@ -1,10 +0,0 @@
-
-class IEditor
-{
-public:
-	virtual ~IEditor() {}
-	virtual void Init(class IGraphics *pGraphics) = 0;
-	virtual void UpdateAndRender() = 0;
-};
-
-extern IEditor *CreateEditor();
diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp
new file mode 100644
index 00000000..7c355fb8
--- /dev/null
+++ b/src/engine/client/graphics.cpp
@@ -0,0 +1,938 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+
+#include <base/detect.h>
+
+#include "SDL.h"
+
+#ifdef CONF_FAMILY_WINDOWS
+	#define WIN32_LEAN_AND_MEAN
+	#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
+
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+#include <engine/shared/engine.h>
+#include <engine/shared/config.h>
+#include <engine/graphics.h>
+#include <engine/storage.h>
+#include <engine/keys.h>
+
+#include <math.h>
+
+#include "graphics.h"
+
+// compressed textures
+#define GL_COMPRESSED_RGB_ARB 0x84ED
+#define GL_COMPRESSED_RGBA_ARB 0x84EE
+#define GL_COMPRESSED_ALPHA_ARB 0x84E9
+
+#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
+
+
+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_OpenGL::Flush()
+{
+	if(m_NumVertices == 0)
+		return;
+		
+	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+	glVertexPointer(3, GL_FLOAT,
+			sizeof(CVertex),
+			(char*)m_aVertices);
+	glTexCoordPointer(2, GL_FLOAT,
+			sizeof(CVertex),
+			(char*)m_aVertices + sizeof(float)*3);
+	glColorPointer(4, GL_FLOAT,
+			sizeof(CVertex),
+			(char*)m_aVertices + sizeof(float)*5);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glEnableClientState(GL_COLOR_ARRAY);
+	
+	if(m_RenderEnable)
+	{
+		if(m_Drawing == DRAWING_QUADS)
+			glDrawArrays(GL_QUADS, 0, m_NumVertices);
+		else if(m_Drawing == DRAWING_LINES)
+			glDrawArrays(GL_LINES, 0, m_NumVertices);
+	}
+	
+	// Reset pointer
+	m_NumVertices = 0;
+}
+
+void CGraphics_OpenGL::AddVertices(int Count)
+{
+	m_NumVertices += Count;
+	if((m_NumVertices + Count) >= MAX_VERTICES)
+		Flush();
+}
+
+void CGraphics_OpenGL::Rotate4(CPoint *pCenter, CVertex *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 - pCenter->x;
+		y = pPoints[i].m_Pos.y - pCenter->y;
+		pPoints[i].m_Pos.x = x * c - y * s + pCenter->x;
+		pPoints[i].m_Pos.y = x * s + y * c + pCenter->y;
+	}
+}
+
+unsigned char CGraphics_OpenGL::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset)
+{
+	return (pData[(v*w+u)*4+Offset]+
+	pData[(v*w+u+1)*4+Offset]+
+	pData[((v+1)*w+u)*4+Offset]+
+	pData[((v+1)*w+u+1)*4+Offset])/4;
+}	
+
+CGraphics_OpenGL::CGraphics_OpenGL()
+{
+	m_NumVertices = 0;
+	
+	m_ScreenX0 = 0;
+	m_ScreenY0 = 0;
+	m_ScreenX1 = 0;
+	m_ScreenY1 = 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_OpenGL::ClipEnable(int x, int y, int w, int h)
+{
+	//if(no_gfx) return;
+	glScissor(x, ScreenHeight()-(y+h), w, h);
+	glEnable(GL_SCISSOR_TEST);
+}
+
+void CGraphics_OpenGL::ClipDisable()
+{
+	//if(no_gfx) return;
+	glDisable(GL_SCISSOR_TEST);
+}
+	
+void CGraphics_OpenGL::BlendNone()
+{
+	glDisable(GL_BLEND);
+}
+
+void CGraphics_OpenGL::BlendNormal()
+{
+	glEnable(GL_BLEND);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void CGraphics_OpenGL::BlendAdditive()
+{
+	glEnable(GL_BLEND);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+}
+
+int CGraphics_OpenGL::MemoryUsage() const
+{ 
+	return m_TextureMemoryUsage;
+}	
+	
+void CGraphics_OpenGL::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY)
+{
+	m_ScreenX0 = TopLeftX;
+	m_ScreenY0 = TopLeftY;
+	m_ScreenX1 = BottomRightX;
+	m_ScreenY1 = BottomRightY;
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(TopLeftX, BottomRightX, BottomRightY, TopLeftY, 1.0f, 10.f);
+}
+
+void CGraphics_OpenGL::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY)
+{
+	*pTopLeftX = m_ScreenX0;
+	*pTopLeftY = m_ScreenY0;
+	*pBottomRightX = m_ScreenX1;
+	*pBottomRightY = m_ScreenY1;
+}
+
+void CGraphics_OpenGL::LinesBegin()
+{
+	dbg_assert(m_Drawing == 0, "called begin twice");
+	m_Drawing = DRAWING_LINES;
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_OpenGL::LinesEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called end without begin");
+	Flush();
+	m_Drawing = 0;
+}
+
+void CGraphics_OpenGL::LinesDraw(const CLineItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called draw 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_OpenGL::UnloadTexture(int Index)
+{
+	if(Index == m_InvalidTexture)
+		return 0;
+		
+	if(Index < 0)
+		return 0;
+		
+	glDeleteTextures(1, &m_aTextures[Index].m_Tex);
+	m_aTextures[Index].m_Next = m_FirstFreeTexture;
+	m_TextureMemoryUsage -= m_aTextures[Index].m_MemSize;
+	m_FirstFreeTexture = Index;
+	return 0;
+}
+
+
+int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
+{
+	int Mipmap = 1;
+	unsigned char *pTexData = (unsigned char *)pData;
+	unsigned char *pTmpData = 0;
+	int Oglformat = 0;
+	int StoreOglformat = 0;
+	int Tex = 0;
+	
+	// don't waste memory on texture if we are stress testing
+	if(g_Config.m_DbgStress)
+		return 	m_InvalidTexture;
+	
+	// grab texture
+	Tex = m_FirstFreeTexture;
+	m_FirstFreeTexture = m_aTextures[Tex].m_Next;
+	m_aTextures[Tex].m_Next = -1;
+	
+	// resample if needed
+	if(!(Flags&TEXLOAD_NORESAMPLE) && g_Config.m_GfxTextureQuality==0)
+	{
+		if(Width > 16 && Height > 16 && Format == CImageInfo::FORMAT_RGBA)
+		{
+			unsigned char *pTmpData;
+			int c = 0;
+			int x, y;
+
+			pTmpData = (unsigned char *)mem_alloc(Width*Height*4, 1);
+
+			Width/=2;
+			Height/=2;
+
+			for(y = 0; y < Height; y++)
+				for(x = 0; x < Width; x++, c++)
+				{
+					pTmpData[c*4] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 0);
+					pTmpData[c*4+1] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 1);
+					pTmpData[c*4+2] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 2);
+					pTmpData[c*4+3] = Sample(Width*2, Height*2, pTexData, x*2,y*2, 3);
+				}
+			pTexData = pTmpData;
+		}
+	}
+	
+	Oglformat = GL_RGBA;
+	if(Format == CImageInfo::FORMAT_RGB)
+		Oglformat = GL_RGB;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		Oglformat = GL_ALPHA;
+	
+	// upload texture
+	if(g_Config.m_GfxTextureCompression)
+	{
+		StoreOglformat = GL_COMPRESSED_RGBA_ARB;
+		if(StoreFormat == CImageInfo::FORMAT_RGB)
+			StoreOglformat = GL_COMPRESSED_RGB_ARB;
+		else if(StoreFormat == CImageInfo::FORMAT_ALPHA)
+			StoreOglformat = GL_COMPRESSED_ALPHA_ARB;
+	}
+	else
+	{
+		StoreOglformat = GL_RGBA;
+		if(StoreFormat == CImageInfo::FORMAT_RGB)
+			StoreOglformat = GL_RGB;
+		else if(StoreFormat == CImageInfo::FORMAT_ALPHA)
+			StoreOglformat = GL_ALPHA;
+	}
+		
+	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);
+	
+	// calculate memory usage
+	{
+		int PixelSize = 4;
+		if(StoreFormat == CImageInfo::FORMAT_RGB)
+			PixelSize = 3;
+		else if(StoreFormat == CImageInfo::FORMAT_ALPHA)
+			PixelSize = 1;
+
+		m_aTextures[Tex].m_MemSize = Width*Height*PixelSize;
+		if(Mipmap)
+		{
+			while(Width > 2 && Height > 2)
+			{
+				Width>>=1;
+				Height>>=1;
+				m_aTextures[Tex].m_MemSize += Width*Height*PixelSize;
+			}
+		}
+	}
+	
+	m_TextureMemoryUsage += m_aTextures[Tex].m_MemSize;
+	mem_free(pTmpData);
+	return Tex;
+}
+
+// simple uncompressed RGBA loaders
+int CGraphics_OpenGL::LoadTexture(const char *pFilename, int StoreFormat, int Flags)
+{
+	int l = str_length(pFilename);
+	int Id;
+	CImageInfo Img;
+	
+	if(l < 3)
+		return -1;
+	if(LoadPNG(&Img, pFilename))
+	{
+		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);
+		return Id;
+	}
+	
+	return m_InvalidTexture;
+}
+
+int CGraphics_OpenGL::LoadPNG(CImageInfo *pImg, const char *pFilename)
+{
+	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, aCompleteFilename, sizeof(aCompleteFilename));
+	if(File)
+		io_close(File);
+	
+	if(png_open_file(&Png, aCompleteFilename) != PNG_NO_ERROR) // ignore_convention
+	{
+		dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename);
+		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_OpenGL::ScreenshotDirect(const char *pFilename)
+{
+	// fetch image data
+	int y;
+	int w = m_ScreenWidth;
+	int h = m_ScreenHeight;
+	unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*4, 1);
+	unsigned char *pTempRow = pPixelData+w*h*4;
+	glReadPixels(0,0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pPixelData);
+	
+	// flip the pixel because opengl works from bottom left corner
+	for(y = 0; y < h/2; y++)
+	{
+		mem_copy(pTempRow, pPixelData+y*w*4, w*4);
+		mem_copy(pPixelData+y*w*4, pPixelData+(h-y-1)*w*4, w*4);
+		mem_copy(pPixelData+(h-y-1)*w*4, pTempRow,w*4);
+	}
+	
+	// find filename
+	{
+		char aWholePath[1024];
+		png_t Png; // ignore_convention
+
+		IOHANDLE File  = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, aWholePath, sizeof(aWholePath));
+		if(File)
+			io_close(File);
+	
+		// save png
+		dbg_msg("client", "saved screenshot to '%s'", aWholePath);
+		png_open_file_write(&Png, aWholePath); // ignore_convention
+		png_set_data(&Png, w, h, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)pPixelData); // ignore_convention
+		png_close_file(&Png); // ignore_convention
+	}
+
+	// clean up
+	mem_free(pPixelData);
+}
+
+void CGraphics_OpenGL::TextureSet(int TextureID)
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin");
+	if(TextureID == -1)
+	{
+		glDisable(GL_TEXTURE_2D);
+	}
+	else
+	{
+		glEnable(GL_TEXTURE_2D);
+		glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex);
+	}
+}
+
+void CGraphics_OpenGL::Clear(float r, float g, float b)
+{
+	glClearColor(r,g,b,0.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void CGraphics_OpenGL::QuadsBegin()
+{
+	dbg_assert(m_Drawing == 0, "called quads_begin twice");
+	m_Drawing = DRAWING_QUADS;
+	
+	QuadsSetSubset(0,0,1,1);
+	QuadsSetRotation(0);
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_OpenGL::QuadsEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_end without begin");
+	Flush();
+	m_Drawing = 0;
+}
+
+void CGraphics_OpenGL::QuadsSetRotation(float Angle)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin");
+	m_Rotation = Angle;
+}
+
+void CGraphics_OpenGL::SetColorVertex(const CColorVertex *pArray, int Num)
+{
+	dbg_assert(m_Drawing != 0, "called gfx_quads_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_OpenGL::SetColor(float r, float g, float b, float a)
+{
+	dbg_assert(m_Drawing != 0, "called gfx_quads_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_OpenGL::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_OpenGL::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_OpenGL::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_OpenGL::QuadsDrawTL(const CQuadItem *pArray, int Num)
+{
+	CPoint Center;
+
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		Center.x = pArray[i].m_X + pArray[i].m_Width/2;
+		Center.y = pArray[i].m_Y + pArray[i].m_Height/2;
+		Center.z = 0;
+		
+		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)
+			Rotate4(&Center, &m_aVertices[m_NumVertices + 4*i]);
+	}
+
+	AddVertices(4*Num);
+}
+
+void CGraphics_OpenGL::QuadsDrawFreeform(const CFreeformItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called quads_draw_freeform 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_OpenGL::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();
+}
+
+bool CGraphics_OpenGL::Init()
+{
+	m_pStorage = Kernel()->RequestInterface<IStorage>();
+	
+	// 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;
+
+	// 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);
+
+	// 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);
+	dbg_msg("", "invalid texture id: %d %d", m_InvalidTexture, m_aTextures[m_InvalidTexture].m_Tex);
+	
+	return true;
+}
+
+int CGraphics_SDL::TryInit()
+{
+	const SDL_VideoInfo *pInfo;
+	int Flags = SDL_OPENGL;
+	
+	m_ScreenWidth = g_Config.m_GfxScreenWidth;
+	m_ScreenHeight = g_Config.m_GfxScreenHeight;
+
+	pInfo = SDL_GetVideoInfo();
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+
+	// set flags
+	Flags  = SDL_OPENGL;
+	Flags |= SDL_GL_DOUBLEBUFFER;
+	Flags |= SDL_HWPALETTE;
+	if(g_Config.m_DbgResizable)
+		Flags |= SDL_RESIZABLE;
+
+	if(pInfo->hw_available) // ignore_convention
+		Flags |= SDL_HWSURFACE;
+	else
+		Flags |= SDL_SWSURFACE;
+
+	if(pInfo->blit_hw) // ignore_convention
+		Flags |= SDL_HWACCEL;
+
+	if(g_Config.m_GfxFullscreen)
+		Flags |= SDL_FULLSCREEN;
+
+	// set gl attributes
+	if(g_Config.m_GfxFsaaSamples)
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_Config.m_GfxFsaaSamples);
+	}
+	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, g_Config.m_GfxVsync);
+
+	// set caption
+	SDL_WM_SetCaption("Teeworlds", "Teeworlds");
+	
+	// create window
+	m_pScreenSurface = SDL_SetVideoMode(m_ScreenWidth, m_ScreenHeight, 0, Flags);
+	if(m_pScreenSurface == NULL)
+	{
+		dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError());
+		return -1;
+	}
+	
+	return 0;
+}
+
+
+int CGraphics_SDL::InitWindow()
+{
+	if(TryInit() == 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(TryInit() == 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(TryInit() == 0)
+			return 0;
+	}
+
+	dbg_msg("gfx", "out of ideas. failed to init graphics");
+					
+	return -1;		
+}
+
+
+CGraphics_SDL::CGraphics_SDL()
+{
+	m_pScreenSurface = 0;
+}
+
+bool CGraphics_SDL::Init()
+{
+	{
+		int Systems = SDL_INIT_VIDEO;
+		
+		if(g_Config.m_SndEnable)
+			Systems |= SDL_INIT_AUDIO;
+
+		if(g_Config.m_ClEventthread)
+			Systems |= SDL_INIT_EVENTTHREAD;
+		
+		if(SDL_Init(Systems) < 0)
+		{
+			dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError());
+			return true;
+		}
+	}
+	
+	atexit(SDL_Quit); // ignore_convention
+
+	#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
+	
+	if(InitWindow() != 0)
+		return true;
+
+	SDL_ShowCursor(0);
+		
+	CGraphics_OpenGL::Init();
+	
+	MapScreen(0,0,g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight);
+	return false;
+}
+
+void CGraphics_SDL::Shutdown()
+{
+	// TODO: SDL, is this correct?
+	SDL_Quit();
+}
+
+void CGraphics_SDL::Minimize()
+{
+	SDL_WM_IconifyWindow();
+}
+
+void CGraphics_SDL::Maximize()
+{
+	// TODO: SDL
+}
+
+int CGraphics_SDL::WindowActive()
+{
+	return SDL_GetAppState()&SDL_APPINPUTFOCUS;
+}
+
+int CGraphics_SDL::WindowOpen()
+{
+	return SDL_GetAppState()&SDL_APPACTIVE;
+
+}
+
+void CGraphics_SDL::TakeScreenshot()
+{
+	m_DoScreenshot = true;
+}
+
+void CGraphics_SDL::Swap()
+{
+	if(m_DoScreenshot)
+	{
+		// find filename
+		char aFilename[128];
+		static int Index = 1;
+
+		for(; Index < 10000; Index++)
+		{
+			IOHANDLE io;
+			str_format(aFilename, sizeof(aFilename), "screenshots/screenshot%05d.png", Index);
+			io = m_pStorage->OpenFile(aFilename, IOFLAG_READ);
+			if(io)
+				io_close(io);
+			else
+				break;
+		}
+
+		ScreenshotDirect(aFilename);
+		m_DoScreenshot = false;
+	}
+	
+	SDL_GL_SwapBuffers();
+	
+	if(g_Config.m_GfxFinish)
+		glFinish();		
+}
+
+
+int CGraphics_SDL::GetVideoModes(CVideoMode *pModes, int MaxModes)
+{
+	int NumModes = sizeof(g_aFakeModes)/sizeof(CVideoMode);
+	SDL_Rect **ppModes;
+
+	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;
+	}
+	
+	// TODO: fix this code on osx or windows
+		
+	ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN);
+	if(ppModes == NULL)
+	{
+		// no modes
+		NumModes = 0;
+	}
+	else if(ppModes == (SDL_Rect**)-1)
+	{
+		// all modes
+	}
+	else
+	{
+		NumModes = 0;
+		for(int i = 0; ppModes[i]; ++i)
+		{
+			if(NumModes == MaxModes)
+				break;
+			pModes[NumModes].m_Width = ppModes[i]->w;
+			pModes[NumModes].m_Height = ppModes[i]->h;
+			pModes[NumModes].m_Red = 8;
+			pModes[NumModes].m_Green = 8;
+			pModes[NumModes].m_Blue = 8;
+			NumModes++;
+		}
+	}
+	
+	return NumModes;
+}
+
+extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); }
diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h
index 80dbf1b9..ff4c3562 100644
--- a/src/engine/client/graphics.h
+++ b/src/engine/client/graphics.h
@@ -1,69 +1,143 @@
-#include "../e_if_gfx.h"
+#ifndef ENGINE_CLIENT_GRAPHICS_H
+#define ENGINE_CLIENT_GRAPHICS_H
 
-class IGraphics
+class CGraphics_OpenGL : public IEngineGraphics
 {
 protected:
-	int m_ScreenWidth;
-	int m_ScreenHeight;
-public:
-	virtual ~IGraphics() {}
-	
-	int ScreenWidth() const { return m_ScreenWidth; }
-	int ScreenHeight() const { return m_ScreenHeight; }
-	float ScreenAspect() const { return (float)ScreenWidth()/(float)ScreenHeight(); }
-	
-	virtual void Clear(float r, float g, float b) = 0;
-	
-	virtual void ClipEnable(int x, int y, int w, int h) = 0;
-	virtual void ClipDisable() = 0;
-	
-	virtual void MapScreen(float tl_x, float tl_y, float br_x, float br_y) = 0;
-	virtual void GetScreen(float *tl_x, float *tl_y, float *br_x, float *br_y) = 0;
+	class IStorage *m_pStorage;
 	
-	virtual void BlendNone() = 0;
-	virtual void BlendNormal() = 0;
-	virtual void BlendAdditive() = 0;
-	
-	virtual int LoadPNG(IMAGE_INFO *pImg, const char *pFilename) =0;
-	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 StoreFormat, int Flags) = 0;
-	virtual void TextureSet(int TextureID) = 0;
+	//
+	typedef struct { float x, y, z; } CPoint;
+	typedef struct { float u, v; } CTexCoord;
+	typedef struct { float r, g, b, a; } CColor;
+
+	typedef struct
+	{
+		CPoint m_Pos;
+		CTexCoord m_Tex;
+		CColor m_Color;
+	} CVertex;
 	
-	virtual void LinesBegin() = 0;
-	virtual void LinesEnd() = 0;
-	virtual void LinesDraw(float x0, float y0, float x1, float y1) = 0;
+	enum
+	{
+		MAX_VERTICES = 32*1024,
+		MAX_TEXTURES = 1024*4,
+		
+		DRAWING_QUADS=1,
+		DRAWING_LINES=2		
+	};
+
+	CVertex m_aVertices[MAX_VERTICES];
+	int m_NumVertices;
+
+	CColor m_aColor[4];
+	CTexCoord m_aTexture[4];
+
+	bool m_RenderEnable;
+
+	float m_Rotation;
+	int m_Drawing;
+	bool m_DoScreenshot;
+
+	float m_ScreenX0;
+	float m_ScreenY0;
+	float m_ScreenX1;
+	float m_ScreenY1;
+
+	int m_InvalidTexture;
+
+	struct CTexture
+	{
+		GLuint m_Tex;
+		int m_MemSize;
+		int m_Flags;
+		int m_Next;
+	};
+
+	CTexture m_aTextures[MAX_TEXTURES];
+	int m_FirstFreeTexture;
+	int m_TextureMemoryUsage;
+
+	void Flush();
+	void AddVertices(int Count);
+	void Rotate4(CPoint *pCenter, CVertex *pPoints);
 	
-	virtual void QuadsBegin() = 0;
-	virtual void QuadsEnd() = 0;
-	virtual void QuadsSetRotation(float Angle) = 0;
-	virtual void QuadsSetSubset(float tl_u, float tl_v, float br_u, float br_v) = 0;
-	virtual void QuadsSetSubsetFree(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) = 0;
+	static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset);
+public:
+	CGraphics_OpenGL();
 	
-	virtual void QuadsDraw(float x, float y, float w, float h) = 0;
-	virtual void QuadsDrawTL(float x, float y, float w, float h) = 0;
-	virtual void QuadsDrawFreeform(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) = 0;
-	virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText) = 0;
+	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 void SetColorVertex(int i, float r, float g, float b, float a) = 0;
-	virtual void SetColor(float r, float g, float b, float a) = 0;
+	virtual int UnloadTexture(int Index);
+	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags);
+
+	// simple uncompressed RGBA loaders
+	virtual int LoadTexture(const char *pFilename, int StoreFormat, int Flags);
+	virtual int LoadPNG(CImageInfo *pImg, const char *pFilename);
+
+	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 TakeScreenshot() = 0;
+	virtual bool Init();
 };
 
-class IEngineGraphics : public IGraphics
+class CGraphics_SDL : public CGraphics_OpenGL
 {
-public:
-	virtual bool Init() = 0;
-	virtual void Shutdown() = 0;
-	virtual void Swap() = 0;
-	
-	virtual void Minimize() = 0;
-	virtual void Maximize() = 0;
+	SDL_Surface *m_pScreenSurface;	
 	
-	virtual int WindowActive() = 0;
-	virtual int WindowOpen() = 0;
+	int TryInit();
+	int InitWindow();
+public:
+	CGraphics_SDL();
+
+	virtual bool Init();
+	virtual void Shutdown();
+
+	virtual void Minimize();
+	virtual void Maximize();
+
+	virtual int WindowActive();
+	virtual int WindowOpen();
+
+	virtual void TakeScreenshot();
+	virtual void Swap();
+
+	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes);
 	
 };
 
-extern IEngineGraphics *CreateEngineGraphics();
+#endif
diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp
new file mode 100644
index 00000000..9f546226
--- /dev/null
+++ b/src/engine/client/input.cpp
@@ -0,0 +1,208 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include "SDL.h"
+
+#include <base/system.h>
+#include <engine/shared/config.h>
+#include <engine/graphics.h>
+#include <engine/input.h>
+#include <engine/keys.h>
+
+#include "input.h"
+
+//print >>f, "int inp_key_code(const char *key_name) { int i; if (!strcmp(key_name, \"-?-\")) return -1; else for (i = 0; i < 512; i++) if (!strcmp(key_strings[i], key_name)) return i; return -1; }"
+
+// this header is protected so you don't include it from anywere
+#define KEYS_INCLUDE
+#include "keynames.h"
+#undef KEYS_INCLUDE
+
+void CInput::AddEvent(int Unicode, int Key, int Flags)
+{
+	if(m_NumEvents != INPUT_BUFFER_SIZE)
+	{
+		m_aInputEvents[m_NumEvents].m_Unicode = Unicode;
+		m_aInputEvents[m_NumEvents].m_Key = Key;
+		m_aInputEvents[m_NumEvents].m_Flags = Flags;
+		m_NumEvents++;
+	}
+}
+
+CInput::CInput()
+{
+	mem_zero(m_aInputCount, sizeof(m_aInputCount));
+	mem_zero(m_aInputState, sizeof(m_aInputState));
+	mem_zero(m_Keys, sizeof(m_Keys));
+
+	m_InputCurrent = 0;
+	m_InputGrabbed = 0;
+
+	m_LastRelease = 0;
+	m_ReleaseDelta = -1;
+
+	m_NumEvents = 0;
+}
+
+void CInput::Init()
+{
+	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
+	SDL_EnableUNICODE(1);
+	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
+}
+
+void CInput::MouseRelative(int *x, int *y)
+{
+	int nx = 0, ny = 0;
+	float Sens = g_Config.m_InpMousesens/100.0f;
+
+	if(g_Config.m_InpGrab)
+		SDL_GetRelativeMouseState(&nx, &ny);
+	else
+	{
+		if(m_InputGrabbed)
+		{
+			SDL_GetMouseState(&nx,&ny);
+			SDL_WarpMouse(Graphics()->ScreenWidth()/2,Graphics()->ScreenHeight()/2);
+			nx -= Graphics()->ScreenWidth()/2; ny -= Graphics()->ScreenHeight()/2;
+		}
+	}
+
+	*x = nx*Sens;
+	*y = ny*Sens;
+}
+
+void CInput::MouseModeAbsolute()
+{
+	SDL_ShowCursor(1);
+	m_InputGrabbed = 0;
+	if(g_Config.m_InpGrab)
+		SDL_WM_GrabInput(SDL_GRAB_OFF);
+}
+
+void CInput::MouseModeRelative()
+{
+	SDL_ShowCursor(0);
+	m_InputGrabbed = 1;
+	if(g_Config.m_InpGrab)
+		SDL_WM_GrabInput(SDL_GRAB_ON);
+}
+
+int CInput::MouseDoubleClick()
+{
+	return m_ReleaseDelta < (time_freq() >> 2);
+}
+
+void CInput::ClearKeyStates()
+{
+	mem_zero(m_aInputState, sizeof(m_aInputState));
+	mem_zero(m_aInputCount, sizeof(m_aInputCount));
+}
+
+int CInput::KeyState(int Key)
+{
+	return m_aInputState[m_InputCurrent][Key];
+}
+
+void CInput::Update()
+{
+	if(m_InputGrabbed && !Graphics()->WindowActive())
+		MouseModeAbsolute();
+
+	/*if(!input_grabbed && Graphics()->WindowActive())
+		Input()->MouseModeRelative();*/
+
+	// clear and begin count on the other one
+	m_InputCurrent^=1;
+	mem_zero(&m_aInputCount[m_InputCurrent], sizeof(m_aInputCount[m_InputCurrent]));
+	mem_zero(&m_aInputState[m_InputCurrent], sizeof(m_aInputState[m_InputCurrent]));
+
+	{
+		int i;
+		Uint8 *pState = SDL_GetKeyState(&i);
+		if(i >= KEY_LAST)
+			i = KEY_LAST-1;
+		mem_copy(m_aInputState[m_InputCurrent], pState, i);
+	}
+
+	// these states must always be updated manually because they are not in the GetKeyState from SDL
+	int i = SDL_GetMouseState(NULL, NULL);
+	if(i&SDL_BUTTON(1)) m_aInputState[m_InputCurrent][KEY_MOUSE_1] = 1; // 1 is left
+	if(i&SDL_BUTTON(3)) m_aInputState[m_InputCurrent][KEY_MOUSE_2] = 1; // 3 is right
+	if(i&SDL_BUTTON(2)) m_aInputState[m_InputCurrent][KEY_MOUSE_3] = 1; // 2 is middle
+	if(i&SDL_BUTTON(4)) m_aInputState[m_InputCurrent][KEY_MOUSE_4] = 1;
+	if(i&SDL_BUTTON(5)) m_aInputState[m_InputCurrent][KEY_MOUSE_5] = 1;
+	if(i&SDL_BUTTON(6)) m_aInputState[m_InputCurrent][KEY_MOUSE_6] = 1;
+	if(i&SDL_BUTTON(7)) m_aInputState[m_InputCurrent][KEY_MOUSE_7] = 1;
+	if(i&SDL_BUTTON(8)) m_aInputState[m_InputCurrent][KEY_MOUSE_8] = 1;
+
+	{
+		SDL_Event Event;
+
+		while(SDL_PollEvent(&Event))
+		{
+			int Key = -1;
+			int Action = IInput::FLAG_PRESS;
+			switch (Event.type)
+			{
+				// handle keys
+				case SDL_KEYDOWN:
+					AddEvent(Event.key.keysym.unicode, 0, 0); // ignore_convention
+                    if(Event.key.keysym.unicode != 0 && Event.key.keysym.unicode < 256) // ignore_convention
+                    {
+                        Key = Event.key.keysym.unicode;  // ignore_convention
+                        m_Keys[Event.key.keysym.sym] = Event.key.keysym.unicode; // ignore_convention
+                    }
+                    else
+                        Key = Event.key.keysym.sym;  // ignore_convention
+					break;
+				case SDL_KEYUP:
+					Action = IInput::FLAG_RELEASE;
+					if(m_Keys[Event.key.keysym.sym] != 0) // ignore_convention
+                        Key = m_Keys[Event.key.keysym.sym]; // ignore_convention
+                    else
+                        Key = Event.key.keysym.sym; // ignore_convention
+					break;
+
+				// handle mouse buttons
+				case SDL_MOUSEBUTTONUP:
+					Action = IInput::FLAG_RELEASE;
+
+					if(Event.button.button == 1) // ignore_convention
+					{
+						m_ReleaseDelta = time_get() - m_LastRelease;
+						m_LastRelease = time_get();
+					}
+
+					// fall through
+				case SDL_MOUSEBUTTONDOWN:
+					if(Event.button.button == SDL_BUTTON_LEFT) Key = KEY_MOUSE_1; // ignore_convention
+					if(Event.button.button == SDL_BUTTON_RIGHT) Key = KEY_MOUSE_2; // ignore_convention
+					if(Event.button.button == SDL_BUTTON_MIDDLE) Key = KEY_MOUSE_3; // ignore_convention
+					if(Event.button.button == SDL_BUTTON_WHEELUP) Key = KEY_MOUSE_WHEEL_UP; // ignore_convention
+					if(Event.button.button == SDL_BUTTON_WHEELDOWN) Key = KEY_MOUSE_WHEEL_DOWN; // ignore_convention
+					if(Event.button.button == 6) Key = KEY_MOUSE_6; // ignore_convention
+					if(Event.button.button == 7) Key = KEY_MOUSE_7; // ignore_convention
+					if(Event.button.button == 8) Key = KEY_MOUSE_8; // ignore_convention
+					break;
+
+				// other messages
+				case SDL_QUIT:
+					// TODO: cleaner exit
+					exit(0); // ignore_convention
+					break;
+			}
+
+			//
+			if(Key != -1)
+			{
+				m_aInputCount[m_InputCurrent][Key].m_Presses++;
+				if(Action == IInput::FLAG_PRESS)
+					m_aInputState[m_InputCurrent][Key] = 1;
+				AddEvent(0, Key, Action);
+			}
+
+		}
+	}
+}
+
+
+IEngineInput *CreateEngineInput() { return new CInput; }
diff --git a/src/engine/client/input.h b/src/engine/client/input.h
new file mode 100644
index 00000000..bf7739ab
--- /dev/null
+++ b/src/engine/client/input.h
@@ -0,0 +1,37 @@
+#ifndef ENGINE_CLIENT_INPUT_H
+#define ENGINE_CLIENT_INPUT_H
+
+class CInput : public IEngineInput
+{
+	IEngineGraphics *m_pGraphics;
+
+	int m_InputGrabbed;
+
+	unsigned int m_LastRelease;
+	unsigned int m_ReleaseDelta;
+
+	int m_Keys[1024];
+
+	void AddEvent(int Unicode, int Key, int Flags);
+
+	IEngineGraphics *Graphics() { return m_pGraphics; }
+
+public:
+	CInput();
+
+	virtual void Init();
+
+	virtual void MouseRelative(int *x, int *y);
+	virtual void MouseModeAbsolute();
+	virtual void MouseModeRelative();
+	virtual int MouseDoubleClick();
+
+	void ClearKeyStates();
+	int KeyState(int Key);
+
+	int ButtonPressed(int Button) { return m_aInputState[m_InputCurrent][Button]; }
+
+	virtual void Update();
+};
+
+#endif
diff --git a/src/engine/client/keynames.h b/src/engine/client/keynames.h
new file mode 100644
index 00000000..2f159a5a
--- /dev/null
+++ b/src/engine/client/keynames.h
@@ -0,0 +1,524 @@
+/* AUTO GENERATED! DO NOT EDIT MANUALLY! */
+
+#ifndef KEYS_INCLUDE
+#error do not include this header!
+#endif
+
+#include <string.h>
+
+const char g_aaKeyStrings[512][16] =
+{
+	"first",
+	"&1",
+	"&2",
+	"&3",
+	"&4",
+	"&5",
+	"&6",
+	"&7",
+	"backspace",
+	"tab",
+	"&10",
+	"&11",
+	"clear",
+	"return",
+	"&14",
+	"&15",
+	"&16",
+	"&17",
+	"&18",
+	"pause",
+	"&20",
+	"&21",
+	"&22",
+	"&23",
+	"&24",
+	"&25",
+	"&26",
+	"escape",
+	"&28",
+	"&29",
+	"&30",
+	"&31",
+	"space",
+	"exclaim",
+	"quotedbl",
+	"hash",
+	"dollar",
+	"&37",
+	"ampersand",
+	"quote",
+	"leftparen",
+	"rightparen",
+	"asterisk",
+	"plus",
+	"comma",
+	"minus",
+	"period",
+	"slash",
+	"0",
+	"1",
+	"2",
+	"3",
+	"4",
+	"5",
+	"6",
+	"7",
+	"8",
+	"9",
+	"colon",
+	"semicolon",
+	"less",
+	"equals",
+	"greater",
+	"question",
+	"at",
+	"&65",
+	"&66",
+	"&67",
+	"&68",
+	"&69",
+	"&70",
+	"&71",
+	"&72",
+	"&73",
+	"&74",
+	"&75",
+	"&76",
+	"&77",
+	"&78",
+	"&79",
+	"&80",
+	"&81",
+	"&82",
+	"&83",
+	"&84",
+	"&85",
+	"&86",
+	"&87",
+	"&88",
+	"&89",
+	"&90",
+	"leftbracket",
+	"backslash",
+	"rightbracket",
+	"caret",
+	"underscore",
+	"backquote",
+	"a",
+	"b",
+	"c",
+	"d",
+	"e",
+	"f",
+	"g",
+	"h",
+	"i",
+	"j",
+	"k",
+	"l",
+	"m",
+	"n",
+	"o",
+	"p",
+	"q",
+	"r",
+	"s",
+	"t",
+	"u",
+	"v",
+	"w",
+	"x",
+	"y",
+	"z",
+	"&123",
+	"&124",
+	"&125",
+	"&126",
+	"delete",
+	"&128",
+	"&129",
+	"&130",
+	"&131",
+	"&132",
+	"&133",
+	"&134",
+	"&135",
+	"&136",
+	"&137",
+	"&138",
+	"&139",
+	"&140",
+	"&141",
+	"&142",
+	"&143",
+	"&144",
+	"&145",
+	"&146",
+	"&147",
+	"&148",
+	"&149",
+	"&150",
+	"&151",
+	"&152",
+	"&153",
+	"&154",
+	"&155",
+	"&156",
+	"&157",
+	"&158",
+	"&159",
+	"world_0",
+	"world_1",
+	"world_2",
+	"world_3",
+	"world_4",
+	"world_5",
+	"world_6",
+	"world_7",
+	"world_8",
+	"world_9",
+	"world_10",
+	"world_11",
+	"world_12",
+	"world_13",
+	"world_14",
+	"world_15",
+	"world_16",
+	"world_17",
+	"world_18",
+	"world_19",
+	"world_20",
+	"world_21",
+	"world_22",
+	"world_23",
+	"world_24",
+	"world_25",
+	"world_26",
+	"world_27",
+	"world_28",
+	"world_29",
+	"world_30",
+	"world_31",
+	"world_32",
+	"world_33",
+	"world_34",
+	"world_35",
+	"world_36",
+	"world_37",
+	"world_38",
+	"world_39",
+	"world_40",
+	"world_41",
+	"world_42",
+	"world_43",
+	"world_44",
+	"world_45",
+	"world_46",
+	"world_47",
+	"world_48",
+	"world_49",
+	"world_50",
+	"world_51",
+	"world_52",
+	"world_53",
+	"world_54",
+	"world_55",
+	"world_56",
+	"world_57",
+	"world_58",
+	"world_59",
+	"world_60",
+	"world_61",
+	"world_62",
+	"world_63",
+	"world_64",
+	"world_65",
+	"world_66",
+	"world_67",
+	"world_68",
+	"world_69",
+	"world_70",
+	"world_71",
+	"world_72",
+	"world_73",
+	"world_74",
+	"world_75",
+	"world_76",
+	"world_77",
+	"world_78",
+	"world_79",
+	"world_80",
+	"world_81",
+	"world_82",
+	"world_83",
+	"world_84",
+	"world_85",
+	"world_86",
+	"world_87",
+	"world_88",
+	"world_89",
+	"world_90",
+	"world_91",
+	"world_92",
+	"world_93",
+	"world_94",
+	"world_95",
+	"kp0",
+	"kp1",
+	"kp2",
+	"kp3",
+	"kp4",
+	"kp5",
+	"kp6",
+	"kp7",
+	"kp8",
+	"kp9",
+	"kp_period",
+	"kp_divide",
+	"kp_multiply",
+	"kp_minus",
+	"kp_plus",
+	"kp_enter",
+	"kp_equals",
+	"up",
+	"down",
+	"right",
+	"left",
+	"insert",
+	"home",
+	"end",
+	"pageup",
+	"pagedown",
+	"f1",
+	"f2",
+	"f3",
+	"f4",
+	"f5",
+	"f6",
+	"f7",
+	"f8",
+	"f9",
+	"f10",
+	"f11",
+	"f12",
+	"f13",
+	"f14",
+	"f15",
+	"&297",
+	"&298",
+	"&299",
+	"numlock",
+	"capslock",
+	"scrollock",
+	"rshift",
+	"lshift",
+	"rctrl",
+	"lctrl",
+	"ralt",
+	"lalt",
+	"rmeta",
+	"lmeta",
+	"lsuper",
+	"rsuper",
+	"mode",
+	"compose",
+	"help",
+	"print",
+	"sysreq",
+	"break",
+	"menu",
+	"power",
+	"euro",
+	"undo",
+	"mouse1",
+	"mouse2",
+	"mouse3",
+	"mouse4",
+	"mouse5",
+	"mouse6",
+	"mouse7",
+	"mouse8",
+	"mousewheelup",
+	"mousewheeldown",
+	"&333",
+	"&334",
+	"&335",
+	"&336",
+	"&337",
+	"&338",
+	"&339",
+	"&340",
+	"&341",
+	"&342",
+	"&343",
+	"&344",
+	"&345",
+	"&346",
+	"&347",
+	"&348",
+	"&349",
+	"&350",
+	"&351",
+	"&352",
+	"&353",
+	"&354",
+	"&355",
+	"&356",
+	"&357",
+	"&358",
+	"&359",
+	"&360",
+	"&361",
+	"&362",
+	"&363",
+	"&364",
+	"&365",
+	"&366",
+	"&367",
+	"&368",
+	"&369",
+	"&370",
+	"&371",
+	"&372",
+	"&373",
+	"&374",
+	"&375",
+	"&376",
+	"&377",
+	"&378",
+	"&379",
+	"&380",
+	"&381",
+	"&382",
+	"&383",
+	"&384",
+	"&385",
+	"&386",
+	"&387",
+	"&388",
+	"&389",
+	"&390",
+	"&391",
+	"&392",
+	"&393",
+	"&394",
+	"&395",
+	"&396",
+	"&397",
+	"&398",
+	"&399",
+	"&400",
+	"&401",
+	"&402",
+	"&403",
+	"&404",
+	"&405",
+	"&406",
+	"&407",
+	"&408",
+	"&409",
+	"&410",
+	"&411",
+	"&412",
+	"&413",
+	"&414",
+	"&415",
+	"&416",
+	"&417",
+	"&418",
+	"&419",
+	"&420",
+	"&421",
+	"&422",
+	"&423",
+	"&424",
+	"&425",
+	"&426",
+	"&427",
+	"&428",
+	"&429",
+	"&430",
+	"&431",
+	"&432",
+	"&433",
+	"&434",
+	"&435",
+	"&436",
+	"&437",
+	"&438",
+	"&439",
+	"&440",
+	"&441",
+	"&442",
+	"&443",
+	"&444",
+	"&445",
+	"&446",
+	"&447",
+	"&448",
+	"&449",
+	"&450",
+	"&451",
+	"&452",
+	"&453",
+	"&454",
+	"&455",
+	"&456",
+	"&457",
+	"&458",
+	"&459",
+	"&460",
+	"&461",
+	"&462",
+	"&463",
+	"&464",
+	"&465",
+	"&466",
+	"&467",
+	"&468",
+	"&469",
+	"&470",
+	"&471",
+	"&472",
+	"&473",
+	"&474",
+	"&475",
+	"&476",
+	"&477",
+	"&478",
+	"&479",
+	"&480",
+	"&481",
+	"&482",
+	"&483",
+	"&484",
+	"&485",
+	"&486",
+	"&487",
+	"&488",
+	"&489",
+	"&490",
+	"&491",
+	"&492",
+	"&493",
+	"&494",
+	"&495",
+	"&496",
+	"&497",
+	"&498",
+	"&499",
+	"&500",
+	"&501",
+	"&502",
+	"&503",
+	"&504",
+	"&505",
+	"&506",
+	"&507",
+	"&508",
+	"&509",
+	"&510",
+	"&511",
+};
+
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
new file mode 100644
index 00000000..df8fa66b
--- /dev/null
+++ b/src/engine/client/sound.cpp
@@ -0,0 +1,485 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include <engine/shared/config.h>
+
+#include "SDL.h"
+
+#include "sound.h"
+
+extern "C" { // wavpack
+	#include <engine/external/wavpack/wavpack.h>
+}
+#include <math.h>
+
+enum
+{
+	NUM_SAMPLES = 512,
+	NUM_VOICES = 64,
+	NUM_CHANNELS = 16,
+	
+	MAX_FRAMES = 1024
+};
+
+struct CSample
+{
+	short *m_pData;
+	int m_NumFrames;
+	int m_Rate;
+	int m_Channels;
+	int m_LoopStart;
+	int m_LoopEnd;
+};
+
+struct CChannel
+{
+	int m_Vol;
+	int m_Pan;
+} ;
+
+struct CVoice
+{
+	CSample *m_pSample;
+	CChannel *m_pChannel;
+	int m_Tick;
+	int m_Vol; // 0 - 255
+	int m_Flags;
+	int m_X, m_Y;
+} ;
+
+static CSample m_aSamples[NUM_SAMPLES] = { {0} };
+static CVoice m_aVoices[NUM_VOICES] = { {0} };
+static CChannel m_aChannels[NUM_CHANNELS] = { {255, 0} };
+
+static LOCK m_SoundLock = 0;
+static int m_SoundEnabled = 0;
+
+static int m_CenterX = 0;
+static int m_CenterY = 0;
+
+static int m_MixingRate = 48000;
+static volatile int m_SoundVolume = 100;
+
+static int m_NextVoice = 0;
+
+
+// TODO: there should be a faster way todo this
+static short Int2Short(int i)
+{
+	if(i > 0x7fff)
+		return 0x7fff;
+	else if(i < -0x7fff)
+		return -0x7fff;
+	return i;
+}
+
+static int IntAbs(int i)
+{
+	if(i<0)
+		return -i;
+	return i;
+}
+
+static void Mix(short *pFinalOut, unsigned Frames)
+{
+	int aMixBuffer[MAX_FRAMES*2] = {0};
+	int MasterVol;
+
+	// aquire lock while we are mixing
+	lock_wait(m_SoundLock);
+	
+	MasterVol = m_SoundVolume;
+	
+	for(unsigned i = 0; i < NUM_VOICES; i++)
+	{
+		if(m_aVoices[i].m_pSample)
+		{
+			// mix voice
+			CVoice *v = &m_aVoices[i];
+			int *pOut = aMixBuffer;
+
+			int Step = v->m_pSample->m_Channels; // setup input sources
+			short *pInL = &v->m_pSample->m_pData[v->m_Tick*Step];
+			short *pInR = &v->m_pSample->m_pData[v->m_Tick*Step+1];
+			
+			unsigned End = v->m_pSample->m_NumFrames-v->m_Tick;
+
+			int Rvol = v->m_pChannel->m_Vol;
+			int Lvol = v->m_pChannel->m_Vol;
+
+			// make sure that we don't go outside the sound data
+			if(Frames < End)
+				End = Frames;
+			
+			// check if we have a mono sound
+			if(v->m_pSample->m_Channels == 1)
+				pInR = pInL;
+
+			// volume calculation
+			if(v->m_Flags&ISound::FLAG_POS && v->m_pChannel->m_Pan)
+			{
+				// TODO: we should respect the channel panning value
+				const int Range = 1500; // magic value, remove
+				int dx = v->m_X - m_CenterX;
+				int dy = v->m_Y - m_CenterY;
+				int Dist = sqrtf((float)dx*dx+dy*dy); // float here. nasty
+				int p = IntAbs(dx);
+				if(Dist < Range)
+				{
+					// panning
+					if(dx > 0)
+						Lvol = ((Range-p)*Lvol)/Range;
+					else
+						Rvol = ((Range-p)*Rvol)/Range;
+					
+					// falloff
+					Lvol = (Lvol*(Range-Dist))/Range;
+					Rvol = (Rvol*(Range-Dist))/Range;
+				}
+				else
+				{
+					Lvol = 0;
+					Rvol = 0;
+				}
+			}
+
+			// process all frames
+			for(unsigned s = 0; s < End; s++)
+			{
+				*pOut++ += (*pInL)*Lvol;
+				*pOut++ += (*pInR)*Rvol;
+				pInL += Step;
+				pInR += Step;
+				v->m_Tick++;
+			}
+			
+			// free voice if not used any more
+			if(v->m_Tick == v->m_pSample->m_NumFrames)
+				v->m_pSample = 0;
+			
+		}
+	}
+	
+	
+	// release the lock
+	lock_release(m_SoundLock);
+
+	{
+		// clamp accumulated values
+		// TODO: this seams slow
+		for(unsigned i = 0; i < Frames; i++)
+		{
+			int j = i<<1;
+			int vl = ((aMixBuffer[j]*MasterVol)/101)>>8;
+			int vr = ((aMixBuffer[j+1]*MasterVol)/101)>>8;
+
+			pFinalOut[j] = Int2Short(vl);
+			pFinalOut[j+1] = Int2Short(vr);
+		}
+	}
+
+#if defined(CONF_ARCH_ENDIAN_BIG)
+	swap_endian(pFinalOut, sizeof(short), Frames * 2);
+#endif
+}
+
+static void SdlCallback(void *pUnused, Uint8 *pStream, int Len)
+{
+	(void)pUnused;
+	Mix((short *)pStream, Len/2/2);
+}
+
+
+int CSound::Init()
+{
+	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
+	m_pStorage = Kernel()->RequestInterface<IStorage>();
+	
+	SDL_AudioSpec Format;
+	
+	m_SoundLock = lock_create();
+	
+	if(!g_Config.m_SndEnable)
+		return 0;
+	
+	m_MixingRate = g_Config.m_SndRate;
+
+	// Set 16-bit stereo audio at 22Khz
+	Format.freq = g_Config.m_SndRate; // ignore_convention
+	Format.format = AUDIO_S16; // ignore_convention
+	Format.channels = 2; // ignore_convention
+	Format.samples = g_Config.m_SndBufferSize; // ignore_convention
+	Format.callback = SdlCallback; // ignore_convention
+	Format.userdata = NULL; // ignore_convention
+
+	// Open the audio device and start playing sound!
+	if(SDL_OpenAudio(&Format, NULL) < 0)
+	{
+		dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError());
+		return -1;
+	}
+	else
+		dbg_msg("client/sound", "sound init successful");
+
+	SDL_PauseAudio(0);
+	
+	m_SoundEnabled = 1;
+	Update(); // update the volume
+	return 0;
+}
+
+int CSound::Update()
+{
+	// update volume
+	int WantedVolume = g_Config.m_SndVolume;
+	
+	if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute)
+		WantedVolume = 0;
+	
+	if(WantedVolume != m_SoundVolume)
+	{
+		lock_wait(m_SoundLock);
+		m_SoundVolume = WantedVolume;
+		lock_release(m_SoundLock);
+	}
+	
+	return 0;
+}
+
+int CSound::Shutdown()
+{
+	SDL_CloseAudio();
+	lock_destroy(m_SoundLock);
+	return 0;
+}
+
+int CSound::AllocId()
+{
+	// TODO: linear search, get rid of it
+	for(unsigned SampleId = 0; SampleId < NUM_SAMPLES; SampleId++)
+	{
+		if(m_aSamples[SampleId].m_pData == 0x0)
+			return SampleId;
+	}
+
+	return -1;
+}
+
+void CSound::RateConvert(int SampleId)
+{
+	CSample *pSample = &m_aSamples[SampleId];
+	int NumFrames = 0;
+	short *pNewData = 0;
+	
+	// make sure that we need to convert this sound
+	if(!pSample->m_pData || pSample->m_Rate == m_MixingRate)
+		return;
+
+	// allocate new data
+	NumFrames = (int)((pSample->m_NumFrames/(float)pSample->m_Rate)*m_MixingRate);
+	pNewData = (short *)mem_alloc(NumFrames*pSample->m_Channels*sizeof(short), 1);
+	
+	for(int i = 0; i < NumFrames; i++)
+	{
+		// resample TODO: this should be done better, like linear atleast
+		float a = i/(float)NumFrames;
+		int f = (int)(a*pSample->m_NumFrames);
+		if(f >= pSample->m_NumFrames)
+			f = pSample->m_NumFrames-1;
+		
+		// set new data
+		if(pSample->m_Channels == 1)
+			pNewData[i] = pSample->m_pData[f];
+		else if(pSample->m_Channels == 2)
+		{
+			pNewData[i*2] = pSample->m_pData[f*2];
+			pNewData[i*2+1] = pSample->m_pData[f*2+1];
+		}
+	}
+	
+	// free old data and apply new
+	mem_free(pSample->m_pData);
+	pSample->m_pData = pNewData;
+	pSample->m_NumFrames = NumFrames;
+}
+
+int CSound::ReadData(void *pBuffer, int Size)
+{
+	return io_read(ms_File, pBuffer, Size);
+}
+
+int CSound::LoadWV(const char *pFilename)
+{
+	CSample *pSample;
+	int SampleId = -1;
+	char aError[100];
+	WavpackContext *pContext;
+	
+	// don't waste memory on sound when we are stress testing
+	if(g_Config.m_DbgStress)
+		return -1;
+		
+	// no need to load sound when we are running with no sound
+	if(!m_SoundEnabled)
+		return 1;
+		
+	if(!m_pStorage)
+		return -1;
+
+	ms_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ); // TODO: use system.h stuff for this
+	if(!ms_File)
+	{
+		dbg_msg("sound/wv", "failed to open %s", pFilename);
+		return -1;
+	}
+
+	SampleId = AllocId();
+	if(SampleId < 0)
+		return -1;
+	pSample = &m_aSamples[SampleId];
+
+	pContext = WavpackOpenFileInput(ReadData, aError);
+	if (pContext)
+	{
+		int m_aSamples = WavpackGetNumSamples(pContext);
+		int BitsPerSample = WavpackGetBitsPerSample(pContext);
+		unsigned int SampleRate = WavpackGetSampleRate(pContext);
+		int m_aChannels = WavpackGetNumChannels(pContext);
+		int *pData;
+		int *pSrc;
+		short *pDst;
+		int i;
+
+		pSample->m_Channels = m_aChannels;
+		pSample->m_Rate = SampleRate;
+
+		if(pSample->m_Channels > 2)
+		{
+			dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", pFilename);
+			return -1;
+		}
+
+		/*
+		if(snd->rate != 44100)
+		{
+			dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
+			return -1;
+		}*/
+		
+		if(BitsPerSample != 16)
+		{
+			dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", BitsPerSample, pFilename);
+			return -1;
+		}
+
+		pData = (int *)mem_alloc(4*m_aSamples*m_aChannels, 1);
+		WavpackUnpackSamples(pContext, pData, m_aSamples); // TODO: check return value
+		pSrc = pData;
+		
+		pSample->m_pData = (short *)mem_alloc(2*m_aSamples*m_aChannels, 1);
+		pDst = pSample->m_pData;
+
+		for (i = 0; i < m_aSamples*m_aChannels; i++)
+			*pDst++ = (short)*pSrc++;
+
+		mem_free(pData);
+
+		pSample->m_NumFrames = m_aSamples;
+		pSample->m_LoopStart = -1;
+		pSample->m_LoopEnd = -1;
+	}
+	else
+	{
+		dbg_msg("sound/wv", "failed to open %s: %s", pFilename, aError);
+	}
+
+	io_close(ms_File);
+	ms_File = NULL;
+
+	if(g_Config.m_Debug)
+		dbg_msg("sound/wv", "loaded %s", pFilename);
+
+	RateConvert(SampleId);
+	return SampleId;
+}
+
+void CSound::SetListenerPos(float x, float y)
+{
+	m_CenterX = (int)x;
+	m_CenterY = (int)y;
+}
+	
+
+void CSound::SetChannel(int ChannelId, float Vol, float Pan)
+{
+	m_aChannels[ChannelId].m_Vol = (int)(Vol*255.0f);
+	m_aChannels[ChannelId].m_Pan = (int)(Pan*255.0f); // TODO: this is only on and off right now
+}
+
+int CSound::Play(int ChannelId, int SampleId, int Flags, float x, float y)
+{
+	int VoiceId = -1;
+	int i;
+	
+	lock_wait(m_SoundLock);
+	
+	// search for voice
+	for(i = 0; i < NUM_VOICES; i++)
+	{
+		int id = (m_NextVoice + i) % NUM_VOICES;
+		if(!m_aVoices[id].m_pSample)
+		{
+			VoiceId = id;
+			m_NextVoice = id+1;
+			break;
+		}
+	}
+	
+	// voice found, use it
+	if(VoiceId != -1)
+	{
+		m_aVoices[VoiceId].m_pSample = &m_aSamples[SampleId];
+		m_aVoices[VoiceId].m_pChannel = &m_aChannels[ChannelId];
+		m_aVoices[VoiceId].m_Tick = 0;
+		m_aVoices[VoiceId].m_Vol = 255;
+		m_aVoices[VoiceId].m_Flags = Flags;
+		m_aVoices[VoiceId].m_X = (int)x;
+		m_aVoices[VoiceId].m_Y = (int)y;
+	}
+	
+	lock_release(m_SoundLock);
+	return VoiceId;
+}
+
+int CSound::PlayAt(int ChannelId, int SampleId, int Flags, float x, float y)
+{
+	return Play(ChannelId, SampleId, Flags|ISound::FLAG_POS, x, y);
+}
+
+int CSound::Play(int ChannelId, int SampleId, int Flags)
+{
+	return Play(ChannelId, SampleId, Flags, 0, 0);
+}
+
+void CSound::Stop(int VoiceId)
+{
+	// TODO: a nice fade out
+	lock_wait(m_SoundLock);
+	m_aVoices[VoiceId].m_pSample = 0;
+	lock_release(m_SoundLock);
+}
+
+void CSound::StopAll()
+{
+	// TODO: a nice fade out
+	lock_wait(m_SoundLock);
+	for(int i = 0; i < NUM_VOICES; i++)
+	{
+		m_aVoices[i].m_pSample = 0;
+	}
+	lock_release(m_SoundLock);
+}
+
+IOHANDLE CSound::ms_File = 0;
+
+IEngineSound *CreateEngineSound() { return new CSound; }
+
diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h
new file mode 100644
index 00000000..9c94c6ad
--- /dev/null
+++ b/src/engine/client/sound.h
@@ -0,0 +1,39 @@
+#ifndef ENGINE_CLIENT_SOUND_H
+#define ENGINE_CLIENT_SOUND_H
+
+#include <engine/sound.h>
+#include <engine/storage.h>
+#include <engine/graphics.h>
+#include <engine/shared/engine.h>
+
+class CSound : public IEngineSound
+{
+public:
+	IEngineGraphics *m_pGraphics;
+	IStorage *m_pStorage;
+
+	virtual int Init();
+
+	int Update();
+	int Shutdown();
+	int AllocId();
+
+	static void RateConvert(int SampleId);
+
+	// TODO: Refactor: clean this mess up
+	static IOHANDLE ms_File;
+	static int ReadData(void *pBuffer, int Size);
+
+	virtual int LoadWV(const char *pFilename);
+
+	virtual void SetListenerPos(float x, float y);
+	virtual void SetChannel(int ChannelId, float Vol, float Pan);
+
+	int Play(int ChannelId, int SampleId, int Flags, float x, float y);
+	virtual int PlayAt(int ChannelId, int SampleId, int Flags, float x, float y);
+	virtual int Play(int ChannelId, int SampleId, int Flags);
+	virtual void Stop(int VoiceId);
+	virtual void StopAll();
+};
+
+#endif
diff --git a/src/engine/client/srvbrowse.cpp b/src/engine/client/srvbrowse.cpp
new file mode 100644
index 00000000..e0997467
--- /dev/null
+++ b/src/engine/client/srvbrowse.cpp
@@ -0,0 +1,721 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <algorithm> // sort
+
+#include <base/system.h>
+#include <engine/shared/network.h>
+#include <engine/shared/protocol.h>
+#include <engine/shared/config.h>
+#include <engine/shared/memheap.h>
+#include <engine/shared/engine.h>
+
+#include <engine/masterserver.h>
+#include <engine/config.h>
+
+#include <mastersrv/mastersrv.h>
+
+#include "srvbrowse.h"
+
+class SortWrap
+{
+	typedef bool (CServerBrowser::*SortFunc)(int, int) const;
+	SortFunc m_pfnSort;
+	CServerBrowser *m_pThis;
+public:
+	SortWrap(CServerBrowser *t, SortFunc f) : m_pfnSort(f), m_pThis(t) {}
+	bool operator()(int a, int b) { return (m_pThis->*m_pfnSort)(a, b); }
+};
+
+CServerBrowser::CServerBrowser()
+{
+	m_pMasterServer = 0;
+	m_ppServerlist = 0;
+	m_pSortedServerlist = 0;
+
+	m_NumFavoriteServers = 0;
+
+	mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
+
+	m_pFirstReqServer = 0; // request list
+	m_pLastReqServer = 0;
+	m_NumRequests = 0;
+
+	m_NeedRefresh = 0;
+
+	m_NumSortedServers = 0;
+	m_NumSortedServersCapacity = 0;
+	m_NumServers = 0;
+	m_NumServerCapacity = 0;
+
+	m_Sorthash = 0;
+	m_aFilterString[0] = 0;
+	m_aFilterGametypeString[0] = 0;
+
+	// the token is to keep server refresh separated from each other
+	m_CurrentToken = 1;
+
+	m_ServerlistType = 0;
+	m_BroadcastTime = 0;
+}
+
+void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion)
+{
+	m_pNetClient = pClient;
+	str_copy(m_aNetVersion, pNetVersion, sizeof(m_aNetVersion));
+	m_pMasterServer = Kernel()->RequestInterface<IMasterServer>();
+	IConfig *pConfig = Kernel()->RequestInterface<IConfig>();
+	if(pConfig)
+		pConfig->RegisterCallback(ConfigSaveCallback, this);
+}
+
+const CServerInfo *CServerBrowser::SortedGet(int Index) const
+{
+	if(Index < 0 || Index >= m_NumSortedServers)
+		return 0;
+	return &m_ppServerlist[m_pSortedServerlist[Index]]->m_Info;
+}
+
+
+bool CServerBrowser::SortCompareName(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return str_comp(a->m_Info.m_aName, b->m_Info.m_aName) < 0;
+}
+
+bool CServerBrowser::SortCompareMap(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return str_comp(a->m_Info.m_aMap, b->m_Info.m_aMap) < 0;
+}
+
+bool CServerBrowser::SortComparePing(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return a->m_Info.m_Latency < b->m_Info.m_Latency;
+}
+
+bool CServerBrowser::SortCompareGametype(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return str_comp(a->m_Info.m_aGameType, b->m_Info.m_aGameType) < 0;
+}
+
+bool CServerBrowser::SortCompareProgression(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return a->m_Info.m_Progression < b->m_Info.m_Progression;
+}
+
+bool CServerBrowser::SortCompareNumPlayers(int Index1, int Index2) const
+{
+	CServerEntry *a = m_ppServerlist[Index1];
+	CServerEntry *b = m_ppServerlist[Index2];
+	return a->m_Info.m_NumPlayers < b->m_Info.m_NumPlayers;
+}
+
+void CServerBrowser::Filter()
+{
+	int i = 0, p = 0;
+	m_NumSortedServers = 0;
+
+	// allocate the sorted list
+	if(m_NumSortedServersCapacity < m_NumServers)
+	{
+		if(m_pSortedServerlist)
+			mem_free(m_pSortedServerlist);
+		m_NumSortedServersCapacity = m_NumServers;
+		m_pSortedServerlist = (int *)mem_alloc(m_NumSortedServersCapacity*sizeof(int), 1);
+	}
+
+	// filter the servers
+	for(i = 0; i < m_NumServers; i++)
+	{
+		int Filtered = 0;
+
+		if(g_Config.m_BrFilterEmpty && m_ppServerlist[i]->m_Info.m_NumPlayers == 0)
+			Filtered = 1;
+		else if(g_Config.m_BrFilterFull && m_ppServerlist[i]->m_Info.m_NumPlayers == m_ppServerlist[i]->m_Info.m_MaxPlayers)
+			Filtered = 1;
+		else if(g_Config.m_BrFilterPw && m_ppServerlist[i]->m_Info.m_Flags&SERVER_FLAG_PASSWORD)
+			Filtered = 1;
+		else if(g_Config.m_BrFilterPure &&
+			(str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "DM") != 0 &&
+			str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "TDM") != 0 &&
+			str_comp(m_ppServerlist[i]->m_Info.m_aGameType, "CTF") != 0))
+		{
+			Filtered = 1;
+		}
+		else if(g_Config.m_BrFilterPureMap &&
+			!(str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm1") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm2") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm6") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm7") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm8") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "dm9") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf1") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf2") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf3") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf4") == 0 ||
+			str_comp(m_ppServerlist[i]->m_Info.m_aMap, "ctf5") == 0)
+		)
+		{
+			Filtered = 1;
+		}
+		else if(g_Config.m_BrFilterPing < m_ppServerlist[i]->m_Info.m_Latency)
+			Filtered = 1;
+		else if(g_Config.m_BrFilterCompatversion && str_comp_num(m_ppServerlist[i]->m_Info.m_aVersion, m_aNetVersion, 3) != 0)
+			Filtered = 1;
+		else
+		{
+			if(g_Config.m_BrFilterString[0] != 0)
+			{
+				int MatchFound = 0;
+
+				m_ppServerlist[i]->m_Info.m_QuickSearchHit = 0;
+
+				// match against server name
+				if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aName, g_Config.m_BrFilterString))
+				{
+					MatchFound = 1;
+					m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_SERVERNAME;
+				}
+
+				// match against players
+				for(p = 0; p < m_ppServerlist[i]->m_Info.m_NumPlayers; p++)
+				{
+					if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aPlayers[p].m_aName, g_Config.m_BrFilterString))
+					{
+						MatchFound = 1;
+						m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_PLAYERNAME;
+						break;
+					}
+				}
+
+				// match against map
+				if(str_find_nocase(m_ppServerlist[i]->m_Info.m_aMap, g_Config.m_BrFilterString))
+				{
+					MatchFound = 1;
+					m_ppServerlist[i]->m_Info.m_QuickSearchHit |= IServerBrowser::QUICK_MAPNAME;
+				}
+
+				if(!MatchFound)
+					Filtered = 1;
+			}
+
+			if(!Filtered && g_Config.m_BrFilterGametype[0] != 0)
+			{
+				// match against game type
+				if(!str_find_nocase(m_ppServerlist[i]->m_Info.m_aGameType, g_Config.m_BrFilterGametype))
+					Filtered = 1;
+			}
+		}
+
+		if(Filtered == 0)
+			m_pSortedServerlist[m_NumSortedServers++] = i;
+	}
+}
+
+int CServerBrowser::SortHash() const
+{
+	int i = g_Config.m_BrSort&0xf;
+	i |= g_Config.m_BrFilterEmpty<<4;
+	i |= g_Config.m_BrFilterFull<<5;
+	i |= g_Config.m_BrFilterPw<<6;
+	i |= g_Config.m_BrSortOrder<<7;
+	i |= g_Config.m_BrFilterCompatversion<<8;
+	i |= g_Config.m_BrFilterPure<<9;
+	i |= g_Config.m_BrFilterPureMap<<10;
+	i |= g_Config.m_BrFilterPing<<16;
+	return i;
+}
+
+void CServerBrowser::Sort()
+{
+	int i;
+
+	// create filtered list
+	Filter();
+
+	// sort
+	if(g_Config.m_BrSort == IServerBrowser::SORT_NAME)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName));
+	else if(g_Config.m_BrSort == IServerBrowser::SORT_PING)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing));
+	else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap));
+	else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareNumPlayers));
+	else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype));
+	else if(g_Config.m_BrSort == IServerBrowser::SORT_PROGRESSION)
+		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareProgression));
+
+	// invert the list if requested
+	if(g_Config.m_BrSortOrder)
+	{
+		for(i = 0; i < m_NumSortedServers/2; i++)
+		{
+			int Temp = m_pSortedServerlist[i];
+			m_pSortedServerlist[i] = m_pSortedServerlist[m_NumSortedServers-i-1];
+			m_pSortedServerlist[m_NumSortedServers-i-1] = Temp;
+		}
+	}
+
+	// set indexes
+	for(i = 0; i < m_NumSortedServers; i++)
+		m_ppServerlist[m_pSortedServerlist[i]]->m_Info.m_SortedIndex = i;
+
+	str_copy(m_aFilterGametypeString, g_Config.m_BrFilterGametype, sizeof(m_aFilterGametypeString));
+	str_copy(m_aFilterString, g_Config.m_BrFilterString, sizeof(m_aFilterString));
+	m_Sorthash = SortHash();
+}
+
+void CServerBrowser::RemoveRequest(CServerEntry *pEntry)
+{
+	if(pEntry->m_pPrevReq || pEntry->m_pNextReq || m_pFirstReqServer == pEntry)
+	{
+		if(pEntry->m_pPrevReq)
+			pEntry->m_pPrevReq->m_pNextReq = pEntry->m_pNextReq;
+		else
+			m_pFirstReqServer = pEntry->m_pNextReq;
+
+		if(pEntry->m_pNextReq)
+			pEntry->m_pNextReq->m_pPrevReq = pEntry->m_pPrevReq;
+		else
+			m_pLastReqServer = pEntry->m_pPrevReq;
+
+		pEntry->m_pPrevReq = 0;
+		pEntry->m_pNextReq = 0;
+		m_NumRequests--;
+	}
+}
+
+CServerBrowser::CServerEntry *CServerBrowser::Find(const NETADDR &Addr)
+{
+	CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]];
+
+	for(; pEntry; pEntry = pEntry->m_pNextIp)
+	{
+		if(net_addr_comp(&pEntry->m_Addr, &Addr) == 0)
+			return pEntry;
+	}
+	return (CServerEntry*)0;
+}
+
+void CServerBrowser::QueueRequest(CServerEntry *pEntry)
+{
+	// add it to the list of servers that we should request info from
+	pEntry->m_pPrevReq = m_pLastReqServer;
+	if(m_pLastReqServer)
+		m_pLastReqServer->m_pNextReq = pEntry;
+	else
+		m_pFirstReqServer = pEntry;
+	m_pLastReqServer = pEntry;
+
+	m_NumRequests++;
+}
+
+void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
+{
+	int Fav = pEntry->m_Info.m_Favorite;
+	pEntry->m_Info = Info;
+	pEntry->m_Info.m_Favorite = Fav;
+	pEntry->m_Info.m_NetAddr = pEntry->m_Addr;
+
+	// all these are just for nice compability
+	if(pEntry->m_Info.m_aGameType[0] == '0' && pEntry->m_Info.m_aGameType[1] == 0)
+		str_copy(pEntry->m_Info.m_aGameType, "DM", sizeof(pEntry->m_Info.m_aGameType));
+	else if(pEntry->m_Info.m_aGameType[0] == '1' && pEntry->m_Info.m_aGameType[1] == 0)
+		str_copy(pEntry->m_Info.m_aGameType, "TDM", sizeof(pEntry->m_Info.m_aGameType));
+	else if(pEntry->m_Info.m_aGameType[0] == '2' && pEntry->m_Info.m_aGameType[1] == 0)
+		str_copy(pEntry->m_Info.m_aGameType, "CTF", sizeof(pEntry->m_Info.m_aGameType));
+
+	/*if(!request)
+	{
+		pEntry->m_Info.latency = (time_get()-pEntry->request_time)*1000/time_freq();
+		RemoveRequest(pEntry);
+	}*/
+
+	pEntry->m_GotInfo = 1;
+	Sort();
+}
+
+CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
+{
+	int Hash = Addr.ip[0];
+	CServerEntry *pEntry = 0;
+	int i;
+
+	// create new pEntry
+	pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry));
+	mem_zero(pEntry, sizeof(CServerEntry));
+
+	// set the info
+	pEntry->m_Addr = Addr;
+	pEntry->m_Info.m_NetAddr = Addr;
+
+	pEntry->m_Info.m_Latency = 999;
+	str_format(pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), "%d.%d.%d.%d:%d",
+		Addr.ip[0], Addr.ip[1], Addr.ip[2],
+		Addr.ip[3], Addr.port);
+	str_format(pEntry->m_Info.m_aName, sizeof(pEntry->m_Info.m_aName), "\255%d.%d.%d.%d:%d", // the \255 is to make sure that it's sorted last
+		Addr.ip[0], Addr.ip[1], Addr.ip[2],
+		Addr.ip[3], Addr.port);
+
+	/*if(serverlist_type == IServerBrowser::TYPE_LAN)
+		pEntry->m_Info.latency = (time_get()-broadcast_time)*1000/time_freq();*/
+
+	// check if it's a favorite
+	for(i = 0; i < m_NumFavoriteServers; i++)
+	{
+		if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
+			pEntry->m_Info.m_Favorite = 1;
+	}
+
+	// add to the hash list
+	pEntry->m_pNextIp = m_aServerlistIp[Hash];
+	m_aServerlistIp[Hash] = pEntry;
+
+	if(m_NumServers == m_NumServerCapacity)
+	{
+		CServerEntry **ppNewlist;
+		m_NumServerCapacity += 100;
+		ppNewlist = (CServerEntry **)mem_alloc(m_NumServerCapacity*sizeof(CServerEntry*), 1);
+		mem_copy(ppNewlist, m_ppServerlist, m_NumServers*sizeof(CServerEntry*));
+		mem_free(m_ppServerlist);
+		m_ppServerlist = ppNewlist;
+	}
+
+	// add to list
+	m_ppServerlist[m_NumServers] = pEntry;
+	pEntry->m_Info.m_ServerIndex = m_NumServers;
+	m_NumServers++;
+
+	return pEntry;
+}
+
+void CServerBrowser::Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo)
+{
+	CServerEntry *pEntry = 0;
+	if(Type == IServerBrowser::SET_MASTER_ADD)
+	{
+		if(m_ServerlistType != IServerBrowser::TYPE_INTERNET)
+			return;
+
+		if(!Find(Addr))
+		{
+			pEntry = Add(Addr);
+			QueueRequest(pEntry);
+		}
+	}
+	else if(Type == IServerBrowser::SET_FAV_ADD)
+	{
+		if(m_ServerlistType != IServerBrowser::TYPE_FAVORITES)
+			return;
+
+		if(!Find(Addr))
+		{
+			pEntry = Add(Addr);
+			QueueRequest(pEntry);
+		}
+	}
+	else if(Type == IServerBrowser::SET_TOKEN)
+	{
+		if(Token != m_CurrentToken)
+			return;
+
+		pEntry = Find(Addr);
+		if(!pEntry)
+			pEntry = Add(Addr);
+		if(pEntry)
+		{
+			SetInfo(pEntry, *pInfo);
+			if(m_ServerlistType == IServerBrowser::TYPE_LAN)
+				pEntry->m_Info.m_Latency = (time_get()-m_BroadcastTime)*1000/time_freq();
+			else
+				pEntry->m_Info.m_Latency = (time_get()-pEntry->m_RequestTime)*1000/time_freq();
+			RemoveRequest(pEntry);
+		}
+	}
+	else if(Type == IServerBrowser::SET_OLD_INTERNET)
+	{
+		pEntry = Find(Addr);
+		if(pEntry)
+		{
+			SetInfo(pEntry, *pInfo);
+
+			if(m_ServerlistType == IServerBrowser::TYPE_LAN)
+				pEntry->m_Info.m_Latency = (time_get()-m_BroadcastTime)*1000/time_freq();
+			else
+				pEntry->m_Info.m_Latency = (time_get()-pEntry->m_RequestTime)*1000/time_freq();
+			RemoveRequest(pEntry);
+		}
+	}
+	else if(Type == IServerBrowser::SET_OLD_LAN)
+	{
+		pEntry = Find(Addr);
+		if(pEntry)
+		if(!pEntry)
+			pEntry = Add(Addr);
+		if(pEntry)
+			SetInfo(pEntry, *pInfo);
+	}
+
+	Sort();
+}
+
+void CServerBrowser::Refresh(int Type)
+{
+	// clear out everything
+	m_ServerlistHeap.Reset();
+	m_NumServers = 0;
+	m_NumSortedServers = 0;
+	mem_zero(m_aServerlistIp, sizeof(m_aServerlistIp));
+	m_pFirstReqServer = 0;
+	m_pLastReqServer = 0;
+	m_NumRequests = 0;
+
+	// next token
+	m_CurrentToken = (m_CurrentToken+1)&0xff;
+
+	//
+	m_ServerlistType = Type;
+
+	if(Type == IServerBrowser::TYPE_LAN)
+	{
+		unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO)+1];
+		CNetChunk Packet;
+		int i;
+
+		mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
+		Buffer[sizeof(SERVERBROWSE_GETINFO)] = m_CurrentToken;
+
+		Packet.m_ClientID = -1;
+		mem_zero(&Packet, sizeof(Packet));
+		Packet.m_Address.ip[0] = 255;
+		Packet.m_Address.ip[1] = 255;
+		Packet.m_Address.ip[2] = 255;
+		Packet.m_Address.ip[3] = 255;
+		Packet.m_Flags = NETSENDFLAG_CONNLESS;
+		Packet.m_DataSize = sizeof(Buffer);
+		Packet.m_pData = Buffer;
+		m_BroadcastTime = time_get();
+
+		for(i = 8303; i <= 8310; i++)
+		{
+			Packet.m_Address.port = i;
+			m_pNetClient->Send(&Packet);
+		}
+
+		if(g_Config.m_Debug)
+			dbg_msg("client", "broadcasting for servers");
+	}
+	else if(Type == IServerBrowser::TYPE_INTERNET)
+		m_NeedRefresh = 1;
+	else if(Type == IServerBrowser::TYPE_FAVORITES)
+	{
+		for(int i = 0; i < m_NumFavoriteServers; i++)
+			Set(m_aFavoriteServers[i], IServerBrowser::SET_FAV_ADD, -1, 0);
+	}
+}
+
+void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const
+{
+	//unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];
+	CNetChunk Packet;
+
+	if(g_Config.m_Debug)
+	{
+		dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
+			Addr.ip[0], Addr.ip[1], Addr.ip[2],
+			Addr.ip[3], Addr.port);
+	}
+
+	/*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
+	buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/
+
+	Packet.m_ClientID = -1;
+	Packet.m_Address = Addr;
+	Packet.m_Flags = NETSENDFLAG_CONNLESS;
+	/*p.data_size = sizeof(buffer);
+	p.data = buffer;
+	netclient_send(net, &p);*/
+
+	// send old request style aswell
+	Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO);
+	Packet.m_pData = SERVERBROWSE_OLD_GETINFO;
+	m_pNetClient->Send(&Packet);
+
+	if(pEntry)
+		pEntry->m_RequestTime = time_get();
+}
+
+void CServerBrowser::Request(const NETADDR &Addr) const
+{
+	RequestImpl(Addr, 0);
+}
+
+
+void CServerBrowser::Update()
+{
+	int64 Timeout = time_freq();
+	int64 Now = time_get();
+	int Count;
+	CServerEntry *pEntry, *pNext;
+
+	// do server list requests
+	if(m_NeedRefresh && !m_pMasterServer->IsRefreshing())
+	{
+		NETADDR Addr;
+		CNetChunk Packet;
+		int i;
+
+		m_NeedRefresh = 0;
+
+		mem_zero(&Packet, sizeof(Packet));
+		Packet.m_ClientID = -1;
+		Packet.m_Flags = NETSENDFLAG_CONNLESS;
+		Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST);
+		Packet.m_pData = SERVERBROWSE_GETLIST;
+
+		for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++)
+		{
+			Addr = m_pMasterServer->GetAddr(i);
+			if(!Addr.ip[0] && !Addr.ip[1] && !Addr.ip[2] && !Addr.ip[3])
+				continue;
+
+			Packet.m_Address = Addr;
+			m_pNetClient->Send(&Packet);
+		}
+
+		if(g_Config.m_Debug)
+			dbg_msg("client", "requesting server list");
+	}
+
+	// do timeouts
+	pEntry = m_pFirstReqServer;
+	while(1)
+	{
+		if(!pEntry) // no more entries
+			break;
+
+		pNext = pEntry->m_pNextReq;
+
+		if(pEntry->m_RequestTime && pEntry->m_RequestTime+Timeout < Now)
+		{
+			// timeout
+			RemoveRequest(pEntry);
+			m_NumRequests--;
+		}
+
+		pEntry = pNext;
+	}
+
+	// do timeouts
+	pEntry = m_pFirstReqServer;
+	Count = 0;
+	while(1)
+	{
+		if(!pEntry) // no more entries
+			break;
+
+		// no more then 10 concurrent requests
+		if(Count == g_Config.m_BrMaxRequests)
+			break;
+
+		if(pEntry->m_RequestTime == 0)
+			RequestImpl(pEntry->m_Addr, pEntry);
+
+		Count++;
+		pEntry = pEntry->m_pNextReq;
+	}
+
+	// check if we need to resort
+	// TODO: remove the str_comp
+	if(m_Sorthash != SortHash() || str_comp(m_aFilterString, g_Config.m_BrFilterString) != 0 || str_comp(m_aFilterGametypeString, g_Config.m_BrFilterGametype) != 0)
+		Sort();
+}
+
+
+bool CServerBrowser::IsFavorite(const NETADDR &Addr) const
+{
+	// search for the address
+	int i;
+	for(i = 0; i < m_NumFavoriteServers; i++)
+	{
+		if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
+			return true;
+	}
+	return false;
+}
+
+void CServerBrowser::AddFavorite(const NETADDR &Addr)
+{
+	CServerEntry *pEntry;
+
+	if(m_NumFavoriteServers == MAX_FAVORITES)
+		return;
+
+	// make sure that we don't already have the server in our list
+	for(int i = 0; i < m_NumFavoriteServers; i++)
+	{
+		if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
+			return;
+	}
+
+	// add the server to the list
+	m_aFavoriteServers[m_NumFavoriteServers++] = Addr;
+	pEntry = Find(Addr);
+	if(pEntry)
+		pEntry->m_Info.m_Favorite = 1;
+
+    if(g_Config.m_Debug)
+        dbg_msg("", "added fav, %d.%d.%d.%d:%d", Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3], Addr.port);
+}
+
+void CServerBrowser::RemoveFavorite(const NETADDR &Addr)
+{
+	int i;
+	CServerEntry *pEntry;
+
+	for(i = 0; i < m_NumFavoriteServers; i++)
+	{
+		if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0)
+		{
+			mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i+1], sizeof(NETADDR)*(m_NumFavoriteServers-(i+1)));
+			m_NumFavoriteServers--;
+
+			pEntry = Find(Addr);
+			if(pEntry)
+				pEntry->m_Info.m_Favorite = 0;
+
+			return;
+		}
+	}
+}
+
+
+bool CServerBrowser::IsRefreshingMasters() const
+{
+	return m_pMasterServer->IsRefreshing();
+}
+
+
+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++)
+	{
+		net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr));
+		str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr);
+		pConfig->WriteLine(aBuffer);
+	}
+}
diff --git a/src/engine/client/srvbrowse.h b/src/engine/client/srvbrowse.h
new file mode 100644
index 00000000..1c255792
--- /dev/null
+++ b/src/engine/client/srvbrowse.h
@@ -0,0 +1,111 @@
+#ifndef ENGINE_CLIENT_SRVBROWSE_H
+#define ENGINE_CLIENT_SRVBROWSE_H
+
+#include <engine/serverbrowser.h>
+
+class CServerBrowser : public IServerBrowser
+{
+public:
+	class CServerEntry
+	{
+	public:
+		NETADDR m_Addr;
+		int64 m_RequestTime;
+		int m_GotInfo;
+		CServerInfo m_Info;
+
+		CServerEntry *m_pNextIp; // ip hashed list
+
+		CServerEntry *m_pPrevReq; // request list
+		CServerEntry *m_pNextReq;
+	};
+
+	enum
+	{
+		MAX_FAVORITES=256
+	};
+
+	CServerBrowser();
+
+	// interface functions
+	void Refresh(int Type);
+	bool IsRefreshingMasters() const;
+
+	int NumServers() const { return m_NumServers; }
+
+	int NumSortedServers() const { return m_NumSortedServers; }
+	const CServerInfo *SortedGet(int Index) const;
+
+	bool IsFavorite(const NETADDR &Addr) const;
+	void AddFavorite(const NETADDR &Addr);
+	void RemoveFavorite(const NETADDR &Addr);
+
+	//
+	void Update();
+	void Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo);
+	void Request(const NETADDR &Addr) const;
+
+	void SetBaseInfo(class CNetClient *pClient, const char *pNetVersion);
+
+private:
+	CNetClient *m_pNetClient;
+	IMasterServer *m_pMasterServer;
+	char m_aNetVersion[128];
+
+	CHeap m_ServerlistHeap;
+	CServerEntry **m_ppServerlist;
+	int *m_pSortedServerlist;
+
+	NETADDR m_aFavoriteServers[MAX_FAVORITES];
+	int m_NumFavoriteServers;
+
+	CServerEntry *m_aServerlistIp[256]; // ip hash list
+
+	CServerEntry *m_pFirstReqServer; // request list
+	CServerEntry *m_pLastReqServer;
+	int m_NumRequests;
+
+	int m_NeedRefresh;
+
+	int m_NumSortedServers;
+	int m_NumSortedServersCapacity;
+	int m_NumServers;
+	int m_NumServerCapacity;
+
+	int m_Sorthash;
+	char m_aFilterString[64];
+	char m_aFilterGametypeString[128];
+
+	// the token is to keep server refresh separated from each other
+	int m_CurrentToken;
+
+	int m_ServerlistType;
+	int64 m_BroadcastTime;
+
+	// sorting criterions
+	bool SortCompareName(int Index1, int Index2) const;
+	bool SortCompareMap(int Index1, int Index2) const;
+	bool SortComparePing(int Index1, int Index2) const;
+	bool SortCompareGametype(int Index1, int Index2) const;
+	bool SortCompareProgression(int Index1, int Index2) const;
+	bool SortCompareNumPlayers(int Index1, int Index2) const;
+
+	//
+	void Filter();
+	void Sort();
+	int SortHash() const;
+
+	CServerEntry *Find(const NETADDR &Addr);
+	CServerEntry *Add(const NETADDR &Addr);
+ 
+	void RemoveRequest(CServerEntry *pEntry);
+	void QueueRequest(CServerEntry *pEntry);
+
+	void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const;
+
+	void SetInfo(CServerEntry *pEntry, const CServerInfo &Info);
+
+	static void ConfigSaveCallback(IConfig *pConfig, void *pUserData);
+};
+
+#endif
diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp
new file mode 100644
index 00000000..b05d49f8
--- /dev/null
+++ b/src/engine/client/text.cpp
@@ -0,0 +1,718 @@
+#include <base/system.h>
+#include <base/math.h>
+#include <engine/graphics.h>
+#include <engine/textrender.h>
+
+#ifdef CONF_FAMILY_WINDOWS
+	#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,
+};
+
+
+static int aFontSizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36};
+#define NUM_FONT_SIZES (sizeof(aFontSizes)/sizeof(int))
+
+struct CFontChar
+{
+	int m_Id;
+	
+	// these values are scaled to the pFont size
+	// width * font_size == real_size
+	float m_Width;
+	float m_Height;
+	float m_OffsetX;
+	float m_OffsetY;
+	float m_AdvanceX;
+	
+	float m_aUvs[4];
+	int64 m_TouchTime;
+};
+
+struct CFontSizeData
+{
+	int m_FontSize;
+	FT_Face *m_pFace;
+
+	unsigned m_aTextures[2];
+	int m_TextureWidth;
+	int m_TextureHeight;
+	
+	int m_NumXChars;
+	int m_NumYChars;
+	
+	int m_CharMaxWidth;
+	int m_CharMaxHeight;
+	
+	CFontChar m_aCharacters[MAX_CHARACTERS*MAX_CHARACTERS];
+	
+	int m_CurrentCharacter;	
+};
+
+struct CFont
+{
+	char m_aFilename[128];
+	FT_Face m_FtFace;
+	CFontSizeData m_aSizes[NUM_FONT_SIZES];
+};
+
+
+class CTextRender : public IEngineTextRender
+{
+	IGraphics *m_pGraphics;
+	IGraphics *Graphics() { return m_pGraphics; }
+	
+	int WordLength(const char *pText)
+	{
+		int s = 1;
+		while(1)
+		{
+			if(*pText == 0)
+				return s-1;
+			if(*pText == '\n' || *pText == '\t' || *pText == ' ')
+				return s;
+			pText++;
+			s++;
+		}
+	}
+
+	float m_TextR;
+	float m_TextG;
+	float m_TextB;
+	float m_TextA;
+	
+	int m_FontTextureFormat;
+
+	struct CFont *m_pDefaultFont;
+
+	FT_Library m_FTLibrary;
+	
+	int GetFontSizeIndex(int Pixelsize)
+	{
+		for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
+		{
+			if(aFontSizes[i] >= Pixelsize)
+				return i;
+		}
+		
+		return NUM_FONT_SIZES-1;
+	}
+	
+
+
+	void Grow(unsigned char *pIn, unsigned char *pOut, int w, int h)
+	{
+		for(int y = 0; y < h; y++) 
+			for(int x = 0; x < w; x++) 
+			{ 
+				int c = pIn[y*w+x]; 
+
+				for(int sy = -1; sy <= 1; sy++)
+					for(int sx = -1; sx <= 1; sx++)
+					{
+						int GetX = x+sx;
+						int GetY = y+sy;
+						if (GetX >= 0 && GetY >= 0 && GetX < w && GetY < h)
+						{
+							int Index = GetY*w+GetX;
+							if(pIn[Index] > c)
+								c = pIn[Index]; 
+						}
+					}
+
+				pOut[y*w+x] = c;
+			}
+	}
+
+	void InitTexture(CFontSizeData *pSizeData, int CharWidth, int CharHeight, int Xchars, int Ychars)
+	{
+		static int FontMemoryUsage = 0;
+		int Width = CharWidth*Xchars;
+		int Height = CharHeight*Ychars;
+		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;
+		
+		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);
+	}
+
+	void IncreaseTextureSize(CFontSizeData *pSizeData)
+	{
+		if(pSizeData->m_TextureWidth < pSizeData->m_TextureHeight)
+			pSizeData->m_NumXChars <<= 1;
+		else
+			pSizeData->m_NumYChars <<= 1;
+		InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, pSizeData->m_NumXChars, pSizeData->m_NumYChars);		
+	}
+	
+	
+	// TODO: Refactor: move this into a pFont class
+	void InitIndex(CFont *pFont, int Index)
+	{
+		int OutlineThickness = 1;
+		CFontSizeData *pSizeData = &pFont->m_aSizes[Index];
+		
+		pSizeData->m_FontSize = aFontSizes[Index];
+		FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize);
+		
+		if(pSizeData->m_FontSize >= 18)
+			OutlineThickness = 2;
+			
+		{
+			unsigned GlyphIndex;
+			int MaxH = 0;
+			int MaxW = 0;
+			
+			int Charcode = FT_Get_First_Char(pFont->m_FtFace, &GlyphIndex);
+			while(GlyphIndex != 0)
+			{   
+				// do stuff
+				FT_Load_Glyph(pFont->m_FtFace, GlyphIndex, FT_LOAD_DEFAULT);
+				
+				if(pFont->m_FtFace->glyph->metrics.width > MaxW) MaxW = pFont->m_FtFace->glyph->metrics.width; // ignore_convention
+				if(pFont->m_FtFace->glyph->metrics.height > MaxH) MaxH = pFont->m_FtFace->glyph->metrics.height; // ignore_convention
+				Charcode = FT_Get_Next_Char(pFont->m_FtFace, Charcode, &GlyphIndex);
+			}
+			
+			MaxW = (MaxW>>6)+2+OutlineThickness*2;
+			MaxH = (MaxH>>6)+2+OutlineThickness*2;
+			
+			for(pSizeData->m_CharMaxWidth = 1; pSizeData->m_CharMaxWidth < MaxW; pSizeData->m_CharMaxWidth <<= 1);
+			for(pSizeData->m_CharMaxHeight = 1; pSizeData->m_CharMaxHeight < MaxH; pSizeData->m_CharMaxHeight <<= 1);
+		}
+		
+		//dbg_msg("pFont", "init size %d, texture size %d %d", pFont->sizes[index].font_size, w, h);
+		//FT_New_Face(m_FTLibrary, "data/fonts/vera.ttf", 0, &pFont->ft_face);
+		InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, 8, 8);
+	}
+
+	CFontSizeData *GetSize(CFont *pFont, int Pixelsize)
+	{
+		int Index = GetFontSizeIndex(Pixelsize);
+		if(pFont->m_aSizes[Index].m_FontSize != aFontSizes[Index])
+			InitIndex(pFont, Index);
+		return &pFont->m_aSizes[Index];
+	}
+
+
+	void UploadGlyph(CFontSizeData *pSizeData, int Texnum, int SlotId, int Chr, const void *pData)
+	{
+		int x = (SlotId%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars);
+		int y = (SlotId/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars);
+		
+		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);
+	}
+
+	// 8k of data used for rendering glyphs
+	unsigned char ms_aGlyphData[(4096/64) * (4096/64)];
+	unsigned char ms_aGlyphDataOutlined[(4096/64) * (4096/64)];
+
+	int GetSlot(CFontSizeData *pSizeData)
+	{
+		int CharCount = pSizeData->m_NumXChars*pSizeData->m_NumYChars;
+		if(pSizeData->m_CurrentCharacter < CharCount)
+		{
+			int i = pSizeData->m_CurrentCharacter;
+			pSizeData->m_CurrentCharacter++;
+			return i;
+		}
+
+		// kick out the oldest
+		// TODO: remove this linear search
+		{
+			int Oldest = 0;
+			for(int i = 1; i < CharCount; i++)
+			{
+				if(pSizeData->m_aCharacters[i].m_TouchTime < pSizeData->m_aCharacters[Oldest].m_TouchTime)
+					Oldest = i;
+			}
+			
+			if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq())
+			{
+				IncreaseTextureSize(pSizeData);
+				return GetSlot(pSizeData);
+			}
+			
+			return Oldest;
+		}
+	}
+
+	int RenderGlyph(CFont *pFont, CFontSizeData *pSizeData, int Chr)
+	{
+		FT_Bitmap *pBitmap;
+		int SlotId = 0;
+		int SlotW = pSizeData->m_TextureWidth / pSizeData->m_NumXChars;
+		int SlotH = pSizeData->m_TextureHeight / pSizeData->m_NumYChars;
+		int SlotSize = SlotW*SlotH;
+		int OutlineThickness = 1;
+		int x = 1;
+		int y = 1;
+		int px, py;
+
+		FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize);
+
+		if(FT_Load_Char(pFont->m_FtFace, Chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP))
+		{
+			dbg_msg("pFont", "error loading glyph %d", Chr);
+			return -1;
+		}
+
+		pBitmap = &pFont->m_FtFace->glyph->bitmap; // ignore_convention
+		
+		// fetch slot
+		SlotId = GetSlot(pSizeData);
+		if(SlotId < 0)
+			return -1;
+		
+		// adjust spacing
+		if(pSizeData->m_FontSize >= 18)
+			OutlineThickness = 2;
+		x += OutlineThickness;
+		y += OutlineThickness;
+
+		// prepare glyph data
+		mem_zero(ms_aGlyphData, SlotSize);
+
+		if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention
+		{
+			for(py = 0; py < pBitmap->rows; py++) // ignore_convention
+				for(px = 0; px < pBitmap->width; px++) // ignore_convention
+					ms_aGlyphData[(py+y)*SlotW+px+x] = pBitmap->buffer[py*pBitmap->pitch+px]; // ignore_convention
+		}
+		else if(pBitmap->pixel_mode == FT_PIXEL_MODE_MONO) // ignore_convention
+		{
+			for(py = 0; py < pBitmap->rows; py++)  // ignore_convention
+				for(px = 0; px < pBitmap->width; px++) // ignore_convention
+				{
+					if(pBitmap->buffer[py*pBitmap->pitch+px/8]&(1<<(7-(px%8)))) // ignore_convention
+						ms_aGlyphData[(py+y)*SlotW+px+x] = 255;
+				}
+		}
+
+		if(0) for(py = 0; py < SlotW; py++) 
+			for(px = 0; px < SlotH; px++) 
+				ms_aGlyphData[py*SlotW+px] = 255;
+		
+		// upload the glyph
+		UploadGlyph(pSizeData, 0, SlotId, Chr, ms_aGlyphData);
+		
+		if(OutlineThickness == 1)
+		{
+			Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH);
+			UploadGlyph(pSizeData, 1, SlotId, Chr, ms_aGlyphDataOutlined);
+		}
+		else
+		{
+			Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH);
+			Grow(ms_aGlyphDataOutlined, ms_aGlyphData, SlotW, SlotH);
+			UploadGlyph(pSizeData, 1, SlotId, Chr, ms_aGlyphData);
+		}
+		
+		// set char info
+		{
+			CFontChar *pFontchr = &pSizeData->m_aCharacters[SlotId];
+			float Scale = 1.0f/pSizeData->m_FontSize;
+			float Uscale = 1.0f/pSizeData->m_TextureWidth;
+			float Vscale = 1.0f/pSizeData->m_TextureHeight;
+			int Height = pBitmap->rows + OutlineThickness*2 + 2; // ignore_convention
+			int Width = pBitmap->width + OutlineThickness*2 + 2; // ignore_convention
+			
+			pFontchr->m_Id = Chr;
+			pFontchr->m_Height = Height * Scale;
+			pFontchr->m_Width = Width * Scale;
+			pFontchr->m_OffsetX = (pFont->m_FtFace->glyph->bitmap_left-1) * Scale; // ignore_convention
+			pFontchr->m_OffsetY = (pSizeData->m_FontSize - pFont->m_FtFace->glyph->bitmap_top) * Scale; // ignore_convention
+			pFontchr->m_AdvanceX = (pFont->m_FtFace->glyph->advance.x>>6) * Scale; // ignore_convention
+			
+			pFontchr->m_aUvs[0] = (SlotId%pSizeData->m_NumXChars) / (float)(pSizeData->m_NumXChars);
+			pFontchr->m_aUvs[1] = (SlotId/pSizeData->m_NumXChars) / (float)(pSizeData->m_NumYChars);
+			pFontchr->m_aUvs[2] = pFontchr->m_aUvs[0] + Width*Uscale;
+			pFontchr->m_aUvs[3] = pFontchr->m_aUvs[1] + Height*Vscale;
+		}
+		
+		return SlotId;
+	}
+
+	CFontChar *GetChar(CFont *pFont, CFontSizeData *pSizeData, int Chr)
+	{
+		CFontChar *pFontchr = NULL;
+		
+		// search for the character
+		// TODO: remove this linear search
+		int i;
+		for(i = 0; i < pSizeData->m_CurrentCharacter; i++)
+		{
+			if(pSizeData->m_aCharacters[i].m_Id == Chr)
+			{
+				pFontchr = &pSizeData->m_aCharacters[i];
+				break;
+			}
+		}
+		
+		// check if we need to render the character
+		if(!pFontchr)
+		{
+			int Index = RenderGlyph(pFont, pSizeData, Chr);
+			if(Index >= 0)
+				pFontchr = &pSizeData->m_aCharacters[Index];
+		}
+		
+		// touch the character
+		// TODO: don't call time_get here
+		if(pFontchr)
+			pFontchr->m_TouchTime = time_get();
+			
+		return pFontchr;
+	}
+
+	// must only be called from the rendering function as the pFont must be set to the correct size
+	void RenderSetup(CFont *pFont, int size)
+	{
+		FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, size);
+	}
+
+	float Kerning(CFont *pFont, int Left, int Right)
+	{
+		FT_Vector Kerning = {0,0};
+		FT_Get_Kerning(pFont->m_FtFace, Left, Right, FT_KERNING_DEFAULT, &Kerning);
+		return (Kerning.x>>6);
+	}
+	
+	
+public:
+	CTextRender()
+	{
+		m_pGraphics = 0;
+
+		m_TextR = 1;
+		m_TextG = 1;
+		m_TextB = 1;
+		m_TextA = 1;
+
+		m_pDefaultFont = 0;
+
+		// GL_LUMINANCE can be good for debugging
+		m_FontTextureFormat = GL_ALPHA;
+	}
+		
+	virtual void Init()
+	{
+		m_pGraphics = Kernel()->RequestInterface<IGraphics>();
+		FT_Init_FreeType(&m_FTLibrary);
+	}
+			
+
+	virtual CFont *LoadFont(const char *pFilename)
+	{
+		CFont *pFont = (CFont *)mem_alloc(sizeof(CFont), 1);
+		
+		mem_zero(pFont, sizeof(*pFont));
+		str_copy(pFont->m_aFilename, pFilename, sizeof(pFont->m_aFilename));
+		
+		if(FT_New_Face(m_FTLibrary, pFont->m_aFilename, 0, &pFont->m_FtFace))
+		{
+			mem_free(pFont);
+			return NULL;
+		}
+
+		for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
+			pFont->m_aSizes[i].m_FontSize = -1;
+		
+		dbg_msg("textrender", "loaded pFont from '%s'", pFilename);
+		return pFont;
+	};
+
+	virtual void DestroyFont(CFont *pFont)
+	{
+		mem_free(pFont);
+	}
+
+	virtual void SetDefaultFont(struct CFont *pFont)
+	{
+		dbg_msg("textrender", "default pFont set %p", pFont);
+		m_pDefaultFont = pFont;
+	}
+		
+		
+	virtual void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags)
+	{
+		mem_zero(pCursor, sizeof(*pCursor));
+		pCursor->m_FontSize = FontSize;
+		pCursor->m_StartX = x;
+		pCursor->m_StartY = y;
+		pCursor->m_X = x;
+		pCursor->m_Y = y;
+		pCursor->m_LineCount = 1;
+		pCursor->m_LineWidth = -1;
+		pCursor->m_Flags = Flags;
+		pCursor->m_CharCount = 0;
+	}
+	
+		
+	virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, int MaxWidth)
+	{
+		CTextCursor Cursor;
+		SetCursor(&Cursor, x, y, Size, TEXTFLAG_RENDER);
+		Cursor.m_LineWidth = MaxWidth;
+		TextEx(&Cursor, pText, -1);
+	}
+
+	virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int Length)
+	{
+		CTextCursor Cursor;
+		SetCursor(&Cursor, 0, 0, Size, 0);
+		TextEx(&Cursor, pText, Length);
+		return Cursor.m_X;
+	}
+	
+	virtual float TextLineCount(void *pFontSetV, float Size, const char *pText, int LineWidth)
+	{
+		CTextCursor Cursor;
+		SetCursor(&Cursor, 0, 0, Size, 0);
+		Cursor.m_LineWidth = LineWidth;
+		TextEx(&Cursor, pText, -1);
+		return Cursor.m_LineCount;
+	}
+
+	virtual void TextColor(float r, float g, float b, float a)
+	{
+		m_TextR = r;
+		m_TextG = g;
+		m_TextB = b;
+		m_TextA = a;
+	}
+	
+	virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length)
+	{
+		CFont *pFont = pCursor->m_pFont;
+		CFontSizeData *pSizeData = NULL;
+		
+		//dbg_msg("textrender", "rendering text '%s'", text);
+
+		float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
+		float FakeToScreenX, FakeToScreenY;
+		int ActualX, ActualY;
+
+		int ActualSize;
+		int i;
+		int GotNewLine = 0;
+		float DrawX, DrawY;
+		float CursorX, CursorY;
+		const char *pEnd;
+
+		float Size = pCursor->m_FontSize;
+
+		// to correct coords, convert to screen coords, round, and convert back
+		Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
+		
+		FakeToScreenX = (Graphics()->ScreenWidth()/(ScreenX1-ScreenX0));
+		FakeToScreenY = (Graphics()->ScreenHeight()/(ScreenY1-ScreenY0));
+		ActualX = pCursor->m_X * FakeToScreenX;
+		ActualY = pCursor->m_Y * FakeToScreenY;
+
+		CursorX = ActualX / FakeToScreenX;
+		CursorY = ActualY / FakeToScreenY;
+
+		// same with size
+		ActualSize = Size * FakeToScreenY;
+		Size = ActualSize / FakeToScreenY;
+
+		// fetch pFont data
+		if(!pFont)
+			pFont = m_pDefaultFont;
+		
+		if(!pFont)
+			return;
+
+		pSizeData = GetSize(pFont, ActualSize);
+		RenderSetup(pFont, ActualSize);
+		
+		// set length
+		if(Length < 0)
+			Length = str_length(pText);
+			
+		pEnd = pText + Length;
+
+		// if we don't want to render, we can just skip the first outline pass
+		i = 1;
+		if(pCursor->m_Flags&TEXTFLAG_RENDER)
+			i = 0;
+
+		for(;i < 2; i++)
+		{
+			const char *pCurrent = (char *)pText;
+			const char *pEnd = pCurrent+Length;
+			DrawX = CursorX;
+			DrawY = CursorY;
+
+			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]);
+				else
+					glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[0]);
+
+				Graphics()->QuadsBegin();
+				if (i == 0)
+					Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*m_TextA);
+				else
+					Graphics()->SetColor(m_TextR, m_TextG, m_TextB, m_TextA);
+			}
+
+			while(pCurrent < pEnd)
+			{
+				int NewLine = 0;
+				const char *pBatchEnd = pEnd;
+				if(pCursor->m_LineWidth > 0 && !(pCursor->m_Flags&TEXTFLAG_STOP_AT_END))
+				{
+					int Wlen = min(WordLength((char *)pCurrent), (int)(pEnd-pCurrent));
+					CTextCursor Compare = *pCursor;
+					Compare.m_X = DrawX;
+					Compare.m_Y = DrawY;
+					Compare.m_Flags &= ~TEXTFLAG_RENDER;
+					Compare.m_LineWidth = -1;
+					TextEx(&Compare, pText, Wlen);
+					
+					if(Compare.m_X-DrawX > pCursor->m_LineWidth)
+					{
+						// word can't be fitted in one line, cut it
+						CTextCursor Cutter = *pCursor;
+						Cutter.m_CharCount = 0;
+						Cutter.m_X = DrawX;
+						Cutter.m_Y = DrawY;
+						Cutter.m_Flags &= ~TEXTFLAG_RENDER;
+						Cutter.m_Flags |= TEXTFLAG_STOP_AT_END;
+						
+						TextEx(&Cutter, (const char *)pCurrent, Wlen);
+						Wlen = Cutter.m_CharCount;
+						NewLine = 1;
+						
+						if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next
+							Wlen = 0;
+					}
+					else if(Compare.m_X-pCursor->m_StartX > pCursor->m_LineWidth)
+					{
+						NewLine = 1;
+						Wlen = 0;
+					}
+					
+					pBatchEnd = pCurrent + Wlen;
+				}
+				
+				while(pCurrent < pBatchEnd)
+				{
+					const char *pTmp;
+					float Advance = 0;
+					int Character = 0;
+					int Nextcharacter = 0;
+					CFontChar *pChr;
+
+					// TODO: UTF-8 decode
+					Character = str_utf8_decode(&pCurrent);
+					pTmp = pCurrent;
+					Nextcharacter = str_utf8_decode(&pTmp);
+					
+					if(Character == '\n')
+					{
+						DrawX = pCursor->m_StartX;
+						DrawY += Size;
+						DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign
+						DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY;
+						++pCursor->m_LineCount;
+						continue;
+					}
+
+					pChr = GetChar(pFont, pSizeData, Character);
+
+					if(pChr)
+					{
+						if(pCursor->m_Flags&TEXTFLAG_RENDER)
+						{
+							Graphics()->QuadsSetSubset(pChr->m_aUvs[0], pChr->m_aUvs[1], pChr->m_aUvs[2], pChr->m_aUvs[3]);
+							IGraphics::CQuadItem QuadItem(DrawX+pChr->m_OffsetX*Size, DrawY+pChr->m_OffsetY*Size, pChr->m_Width*Size, pChr->m_Height*Size);
+							Graphics()->QuadsDrawTL(&QuadItem, 1);
+						}
+
+						Advance = pChr->m_AdvanceX + Kerning(pFont, Character, Nextcharacter)/Size;
+					}
+									
+					if(pCursor->m_Flags&TEXTFLAG_STOP_AT_END && DrawX+Advance*Size-pCursor->m_StartX > pCursor->m_LineWidth)
+					{
+						// we hit the end of the line, no more to render or count
+						pCurrent = pEnd;
+						break;
+					}
+
+					DrawX += Advance*Size;
+					pCursor->m_CharCount++;
+				}
+				
+				if(NewLine)
+				{
+					DrawX = pCursor->m_StartX;
+					DrawY += Size;
+					GotNewLine = 1;
+					DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign
+					DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY;				
+					++pCursor->m_LineCount;
+				}
+			}
+
+			if(pCursor->m_Flags&TEXTFLAG_RENDER)
+				Graphics()->QuadsEnd();
+		}
+
+		pCursor->m_X = DrawX;
+		
+		if(GotNewLine)
+			pCursor->m_Y = DrawY;
+	}
+	
+};
+
+IEngineTextRender *CreateEngineTextRender() { return new CTextRender; }