about summary refs log tree commit diff
path: root/src/engine/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/shared')
-rw-r--r--src/engine/shared/compression.cpp88
-rw-r--r--src/engine/shared/compression.h12
-rw-r--r--src/engine/shared/config.cpp111
-rw-r--r--src/engine/shared/config.h22
-rw-r--r--src/engine/shared/config_variables.h80
-rw-r--r--src/engine/shared/console.cpp489
-rw-r--r--src/engine/shared/console.h96
-rw-r--r--src/engine/shared/datafile.cpp643
-rw-r--r--src/engine/shared/datafile.h77
-rw-r--r--src/engine/shared/demorec.cpp623
-rw-r--r--src/engine/shared/demorec.h117
-rw-r--r--src/engine/shared/engine.cpp76
-rw-r--r--src/engine/shared/engine.h23
-rw-r--r--src/engine/shared/huffman.cpp276
-rw-r--r--src/engine/shared/huffman.h89
-rw-r--r--src/engine/shared/jobs.cpp74
-rw-r--r--src/engine/shared/jobs.h51
-rw-r--r--src/engine/shared/kernel.cpp93
-rw-r--r--src/engine/shared/linereader.cpp62
-rw-r--r--src/engine/shared/linereader.h17
-rw-r--r--src/engine/shared/map.cpp45
-rw-r--r--src/engine/shared/masterserver.cpp187
-rw-r--r--src/engine/shared/memheap.cpp96
-rw-r--r--src/engine/shared/memheap.h32
-rw-r--r--src/engine/shared/message.h9
-rw-r--r--src/engine/shared/network.cpp347
-rw-r--r--src/engine/shared/network.h346
-rw-r--r--src/engine/shared/network_client.cpp139
-rw-r--r--src/engine/shared/network_conn.cpp354
-rw-r--r--src/engine/shared/network_server.cpp413
-rw-r--r--src/engine/shared/packer.cpp155
-rw-r--r--src/engine/shared/packer.h42
-rw-r--r--src/engine/shared/protocol.h91
-rw-r--r--src/engine/shared/ringbuffer.cpp192
-rw-r--r--src/engine/shared/ringbuffer.h66
-rw-r--r--src/engine/shared/snapshot.cpp537
-rw-r--r--src/engine/shared/snapshot.h136
-rw-r--r--src/engine/shared/storage.cpp196
38 files changed, 6502 insertions, 0 deletions
diff --git a/src/engine/shared/compression.cpp b/src/engine/shared/compression.cpp
new file mode 100644
index 00000000..63e44699
--- /dev/null
+++ b/src/engine/shared/compression.cpp
@@ -0,0 +1,88 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+
+#include "compression.h"
+
+// Format: ESDDDDDD EDDDDDDD EDD...  Extended, Data, Sign
+unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) 
+{ 
+	*pDst = (i>>25)&0x40; // set sign bit if i<0
+	i = i^(i>>31); // if(i<0) i = ~i
+
+	*pDst |= i&0x3F; // pack 6bit into dst
+	i >>= 6; // discard 6 bits
+	if(i)
+	{
+		*pDst |= 0x80; // set extend bit
+		while(1)
+		{
+			pDst++;
+			*pDst = i&(0x7F); // pack 7bit
+			i >>= 7; // discard 7 bits
+			*pDst |= (i!=0)<<7; // set extend bit (may branch)
+			if(!i)
+				break;
+		}
+	}
+
+	pDst++;
+	return pDst; 
+} 
+ 
+const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut)
+{ 
+	int Sign = (*pSrc>>6)&1; 
+	*pInOut = *pSrc&0x3F; 
+
+	do
+	{ 
+		if(!(*pSrc&0x80)) break;
+		pSrc++;
+		*pInOut |= (*pSrc&(0x7F))<<(6);
+
+		if(!(*pSrc&0x80)) break;
+		pSrc++;
+		*pInOut |= (*pSrc&(0x7F))<<(6+7);
+
+		if(!(*pSrc&0x80)) break;
+		pSrc++;
+		*pInOut |= (*pSrc&(0x7F))<<(6+7+7);
+
+		if(!(*pSrc&0x80)) break;
+		pSrc++;
+		*pInOut |= (*pSrc&(0x7F))<<(6+7+7+7);
+	} while(0);
+
+	pSrc++;
+	*pInOut ^= -Sign; // if(sign) *i = ~(*i)
+	return pSrc; 
+} 
+
+
+long CVariableInt::Decompress(const void *pSrc_, int Size, void *pDst_)
+{
+	const unsigned char *pSrc = (unsigned char *)pSrc_;
+	const unsigned char *pEnd = pSrc + Size;
+	int *pDst = (int *)pDst_;
+	while(pSrc < pEnd)
+	{
+		pSrc = CVariableInt::Unpack(pSrc, pDst);
+		pDst++;
+	}
+	return (long)((unsigned char *)pDst-(unsigned char *)pDst_);
+}
+
+long CVariableInt::Compress(const void *pSrc_, int Size, void *pDst_)
+{
+	int *pSrc = (int *)pSrc_;
+	unsigned char *pDst = (unsigned char *)pDst_;
+	Size /= 4;
+	while(Size)
+	{
+		pDst = CVariableInt::Pack(pDst, *pSrc);
+		Size--;
+		pSrc++;
+	}
+	return (long)(pDst-(unsigned char *)pDst_);
+}
+
diff --git a/src/engine/shared/compression.h b/src/engine/shared/compression.h
new file mode 100644
index 00000000..9bd9e61a
--- /dev/null
+++ b/src/engine/shared/compression.h
@@ -0,0 +1,12 @@
+#ifndef ENGINE_SHARED_COMPRESSION_H
+#define ENGINE_SHARED_COMPRESSION_H
+// variable int packing
+class CVariableInt
+{
+public:
+	static unsigned char *Pack(unsigned char *pDst, int i);
+	static const unsigned char *Unpack(const unsigned char *pSrc, int *pInOut);
+	static long Compress(const void *pSrc, int Size, void *pDst);
+	static long Decompress(const void *pSrc, int Size, void *pDst);
+};
+#endif
diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp
new file mode 100644
index 00000000..ca12e8b7
--- /dev/null
+++ b/src/engine/shared/config.cpp
@@ -0,0 +1,111 @@
+#include <engine/config.h>
+#include <engine/storage.h>
+#include <engine/shared/config.h>
+
+CConfiguration g_Config;
+
+class CConfig : public IConfig
+{
+	IStorage *m_pStorage;
+	IOHANDLE m_ConfigFile;
+	
+	struct CCallback
+	{
+		SAVECALLBACKFUNC m_pfnFunc;
+		void *m_pUserData;
+	};
+	
+	enum
+	{
+		MAX_CALLBACKS = 16
+	};
+	
+	CCallback m_aCallbacks[MAX_CALLBACKS];
+	int m_NumCallbacks;
+	
+	void EscapeParam(char *pDst, const char *pSrc, int size)
+	{
+		for(int i = 0; *pSrc && i < size - 1; ++i)
+		{
+			if(*pSrc == '"' || *pSrc == '\\') // escape \ and "
+				*pDst++ = '\\';
+			*pDst++ = *pSrc++;
+		}
+		*pDst = 0;
+	}
+
+public:
+
+	CConfig()
+	{
+		m_ConfigFile = 0;
+		m_NumCallbacks = 0;
+	}
+	
+	virtual void Init()
+	{
+		m_pStorage = Kernel()->RequestInterface<IStorage>();
+		Reset();
+	}
+	
+	virtual void Reset()
+	{
+		#define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) g_Config.m_##Name = def;
+		#define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) str_copy(g_Config.m_##Name, def, len);
+
+		#include "config_variables.h" 
+
+		#undef MACRO_CONFIG_INT 
+		#undef MACRO_CONFIG_STR 		
+	}
+	
+	virtual void Save()
+	{
+		if(!m_pStorage)
+			return;
+		m_ConfigFile = m_pStorage->OpenFile("settings.cfg", IOFLAG_WRITE);
+		
+		if(!m_ConfigFile)
+			return;
+		
+		char aLineBuf[1024*2];
+		char aEscapeBuf[1024*2];
+
+		#define MACRO_CONFIG_INT(Name,ScriptName,def,min,max,flags,desc) if((flags)&CFGFLAG_SAVE){ str_format(aLineBuf, sizeof(aLineBuf), "%s %i", #ScriptName, g_Config.m_##Name); WriteLine(aLineBuf); }
+		#define MACRO_CONFIG_STR(Name,ScriptName,len,def,flags,desc) if((flags)&CFGFLAG_SAVE){ EscapeParam(aEscapeBuf, g_Config.m_##Name, sizeof(aEscapeBuf)); str_format(aLineBuf, sizeof(aLineBuf), "%s \"%s\"", #ScriptName, aEscapeBuf); WriteLine(aLineBuf); }
+
+		#include "config_variables.h" 
+
+		#undef MACRO_CONFIG_INT 
+		#undef MACRO_CONFIG_STR 				
+		
+		for(int i = 0; i < m_NumCallbacks; i++)
+			m_aCallbacks[i].m_pfnFunc(this, m_aCallbacks[i].m_pUserData);
+		
+		io_close(m_ConfigFile);
+		m_ConfigFile = 0;
+	}
+	
+	virtual void RegisterCallback(SAVECALLBACKFUNC pfnFunc, void *pUserData)
+	{
+		dbg_assert(m_NumCallbacks < MAX_CALLBACKS, "too many config callbacks");
+		m_aCallbacks[m_NumCallbacks].m_pfnFunc = pfnFunc;
+		m_aCallbacks[m_NumCallbacks].m_pUserData = pUserData;
+		m_NumCallbacks++;
+	}
+	
+	virtual void WriteLine(const char *pLine)
+	{
+		if(!m_ConfigFile)
+			return;
+#if defined(CONF_FAMILY_WINDOWS)
+		static const char Newline[] = "\r\n";
+#else
+		static const char Newline[] = "\n";
+#endif
+		io_write(m_ConfigFile, pLine, str_length(pLine));
+		io_write(m_ConfigFile, Newline, sizeof(Newline)-1);			
+	}
+};
+
+IConfig *CreateConfig() { return new CConfig; }
diff --git a/src/engine/shared/config.h b/src/engine/shared/config.h
new file mode 100644
index 00000000..10a54004
--- /dev/null
+++ b/src/engine/shared/config.h
@@ -0,0 +1,22 @@
+#ifndef ENGINE_SHARED_E_CONFIG_H
+#define ENGINE_SHARED_E_CONFIG_H
+
+struct CConfiguration
+{ 
+    #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Save,Desc) int m_##Name;
+    #define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Save,Desc) char m_##Name[Len]; // Flawfinder: ignore
+    #include "config_variables.h" 
+    #undef MACRO_CONFIG_INT 
+    #undef MACRO_CONFIG_STR 
+};
+
+extern CConfiguration g_Config;
+
+enum
+{
+	CFGFLAG_SAVE=1,
+	CFGFLAG_CLIENT=2,
+	CFGFLAG_SERVER=4
+};
+
+#endif
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
new file mode 100644
index 00000000..e5541911
--- /dev/null
+++ b/src/engine/shared/config_variables.h
@@ -0,0 +1,80 @@
+#ifndef ENGINE_SHARED_E_CONFIG_VARIABLES_H
+#define ENGINE_SHARED_E_CONFIG_VARIABLES_H
+#undef ENGINE_SHARED_E_CONFIG_VARIABLES_H // this file will be included several times
+
+// TODO: remove this
+#include "././game/variables.h"
+
+
+MACRO_CONFIG_STR(PlayerName, player_name, 24, "nameless tee", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Name of the player")
+MACRO_CONFIG_STR(ClanName, clan_name, 32, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "(not used)")
+MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT|CFGFLAG_SERVER, "Password to the server")
+MACRO_CONFIG_STR(Logfile, logfile, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filename to log all output to")
+
+MACRO_CONFIG_INT(ClCpuThrottle, cl_cpu_throttle, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "")
+
+MACRO_CONFIG_INT(ClEventthread, cl_eventthread, 0, 0, 1, CFGFLAG_CLIENT, "Enables the usage of a thread to pump the events")
+
+MACRO_CONFIG_INT(InpGrab, inp_grab, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use forceful input grabbing method")
+
+MACRO_CONFIG_STR(BrFilterString, br_filter_string, 25, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Server browser filtering string")
+
+MACRO_CONFIG_INT(BrFilterFull, br_filter_full, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out full server in browser")
+MACRO_CONFIG_INT(BrFilterEmpty, br_filter_empty, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out empty server in browser")
+MACRO_CONFIG_INT(BrFilterPw, br_filter_pw, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out password protected servers in browser")
+MACRO_CONFIG_INT(BrFilterPing, br_filter_ping, 999, 0, 999, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ping to filter by in the server browser")
+MACRO_CONFIG_STR(BrFilterGametype, br_filter_gametype, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Game types to filter")
+MACRO_CONFIG_INT(BrFilterPure, br_filter_pure, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard servers in browser")
+MACRO_CONFIG_INT(BrFilterPureMap, br_filter_pure_map, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-standard maps in browser")
+MACRO_CONFIG_INT(BrFilterCompatversion, br_filter_compatversion, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Filter out non-compatible servers in browser")
+
+MACRO_CONFIG_INT(BrSort, br_sort, 0, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(BrSortOrder, br_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(BrMaxRequests, br_max_requests, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser")
+
+MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size")
+MACRO_CONFIG_INT(SndRate, snd_rate, 48000, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate")
+MACRO_CONFIG_INT(SndEnable, snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound enable")
+MACRO_CONFIG_INT(SndVolume, snd_volume, 100, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound volume")
+MACRO_CONFIG_INT(SndDevice, snd_device, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "(deprecated) Sound device to use")
+
+MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+
+MACRO_CONFIG_INT(GfxScreenWidth, gfx_screen_width, 800, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution width")
+MACRO_CONFIG_INT(GfxScreenHeight, gfx_screen_height, 600, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen resolution height")
+MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Fullscreen")
+MACRO_CONFIG_INT(GfxAlphabits, gfx_alphabits, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Alpha bits for framebuffer  (fullscreen only)")
+MACRO_CONFIG_INT(GfxColorDepth, gfx_color_depth, 24, 16, 24, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Colors bits for framebuffer (fullscreen only)")
+MACRO_CONFIG_INT(GfxClear, gfx_clear, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Clear screen before rendering")
+MACRO_CONFIG_INT(GfxVsync, gfx_vsync, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Vertical sync")
+MACRO_CONFIG_INT(GfxDisplayAllModes, gfx_display_all_modes, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(GfxTextureCompression, gfx_texture_compression, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Use texture compression")
+MACRO_CONFIG_INT(GfxHighDetail, gfx_high_detail, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "High detail")
+MACRO_CONFIG_INT(GfxTextureQuality, gfx_texture_quality, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE|CFGFLAG_CLIENT, "FSAA Samples")
+MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Screen refresh rate")
+MACRO_CONFIG_INT(GfxFinish, gfx_finish, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
+
+MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 100, 5, 100000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Mouse sensitivity")
+
+MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server name")
+MACRO_CONFIG_STR(SvBindaddr, sv_bindaddr, 128, "", CFGFLAG_SERVER, "Address to bind the server to")
+MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server")
+MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers")
+MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server")
+MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server")
+MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only")
+MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing")
+MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password")
+MACRO_CONFIG_INT(SvMapReload, sv_map_reload, 0, 0, 1, CFGFLAG_SERVER, "Reload the current map")
+
+MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode")
+MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems")
+MACRO_CONFIG_INT(DbgStressNetwork, dbg_stress_network, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress network")
+MACRO_CONFIG_INT(DbgPref, dbg_pref, 0, 0, 1, CFGFLAG_SERVER, "Performance outputs")
+MACRO_CONFIG_INT(DbgGraphs, dbg_graphs, 0, 0, 1, CFGFLAG_CLIENT, "Performance graphs")
+MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings")
+MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress")
+MACRO_CONFIG_INT(DbgResizable, dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing")
+#endif
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
new file mode 100644
index 00000000..c545b7db
--- /dev/null
+++ b/src/engine/shared/console.cpp
@@ -0,0 +1,489 @@
+#include <base/system.h>
+#include <engine/shared/protocol.h>
+#include <engine/storage.h>
+#include "console.h"
+#include "config.h"
+#include "engine.h"
+#include "linereader.h"
+
+const char *CConsole::CResult::GetString(unsigned Index)
+{
+	if (Index < 0 || Index >= m_NumArgs)
+		return "";
+	return m_apArgs[Index];
+}
+
+int CConsole::CResult::GetInteger(unsigned Index)
+{
+	if (Index < 0 || Index >= m_NumArgs)
+		return 0;
+	return str_toint(m_apArgs[Index]);
+}
+
+float CConsole::CResult::GetFloat(unsigned Index)
+{
+	if (Index < 0 || Index >= m_NumArgs)
+		return 0.0f;
+	return str_tofloat(m_apArgs[Index]);
+}
+
+// the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces
+static char *SkipBlanks(char *pStr)
+{
+	while(*pStr && (*pStr == ' ' || *pStr == '\t' || *pStr == '\n'))
+		pStr++;
+	return pStr;
+}
+
+static char *SkipToBlank(char *pStr)
+{
+	while(*pStr && (*pStr != ' ' && *pStr != '\t' && *pStr != '\n'))
+		pStr++;
+	return pStr;
+}
+
+
+int CConsole::ParseStart(CResult *pResult, const char *pString, int Length)
+{
+	char *pStr;
+	int Len = sizeof(pResult->m_aStringStorage);
+	if(Length < Len)
+		Len = Length;
+		
+	str_copy(pResult->m_aStringStorage, pString, Length);
+	pStr = pResult->m_aStringStorage;
+	
+	// get command
+	pStr = SkipBlanks(pStr);
+	pResult->m_pCommand = pStr;
+	pStr = SkipToBlank(pStr);
+	
+	if(*pStr)
+	{
+		pStr[0] = 0;
+		pStr++;
+	}
+	
+	pResult->m_pArgsStart = pStr;
+	return 0;
+}
+
+int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
+{
+	char Command;
+	char *pStr;
+	int Optional = 0;
+	int Error = 0;
+	
+	pStr = pResult->m_pArgsStart;
+
+	while(1)	
+	{
+		// fetch command
+		Command = *pFormat;
+		pFormat++;
+		
+		if(!Command)
+			break;
+		
+		if(Command == '?')
+			Optional = 1;
+		else
+		{
+			pStr = SkipBlanks(pStr);
+		
+			if(!(*pStr)) // error, non optional command needs value
+			{
+				if(!Optional)
+					Error = 1;
+				break;
+			}
+			
+			// add token
+			if(*pStr == '"')
+			{
+				char *pDst;
+				pStr++;
+				pResult->AddArgument(pStr);
+				
+				pDst = pStr; // we might have to process escape data
+				while(1)
+				{
+					if(pStr[0] == '"')
+						break;
+					else if(pStr[0] == '\\')
+					{
+						if(pStr[1] == '\\')
+							pStr++; // skip due to escape
+						else if(pStr[1] == '"')
+							pStr++; // skip due to escape
+					}
+					else if(pStr[0] == 0)
+						return 1; // return error
+						
+					*pDst = *pStr;
+					pDst++;
+					pStr++;
+				}
+				
+				// write null termination
+				*pDst = 0;
+
+				
+				pStr++;
+			}
+			else
+			{
+				pResult->AddArgument(pStr);
+				
+				if(Command == 'r') // rest of the string
+					break;
+				else if(Command == 'i') // validate int
+					pStr = SkipToBlank(pStr);
+				else if(Command == 'f') // validate float
+					pStr = SkipToBlank(pStr);
+				else if(Command == 's') // validate string
+					pStr = SkipToBlank(pStr);
+
+				if(pStr[0] != 0) // check for end of string
+				{
+					pStr[0] = 0;
+					pStr++;
+				}
+			}
+		}
+	}
+
+	return Error;
+}
+
+void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData)
+{
+	m_pfnPrintCallback = pfnPrintCallback;
+	m_pPrintCallbackUserdata = pUserData;
+}
+
+void CConsole::Print(const char *pStr)
+{
+	dbg_msg("console" ,"%s", pStr);
+	if (m_pfnPrintCallback)
+		m_pfnPrintCallback(pStr, m_pPrintCallbackUserdata);
+}
+
+void CConsole::ExecuteLineStroked(int Stroke, const char *pStr)
+{
+	CResult Result;
+	
+	char aStrokeStr[2] = {'0', 0};
+	if(Stroke)
+		aStrokeStr[0] = '1';
+
+	while(pStr && *pStr)
+	{
+		const char *pEnd = pStr;
+		const char *pNextPart = 0;
+		int InString = 0;
+		
+		while(*pEnd)
+		{
+			if(*pEnd == '"')
+				InString ^= 1;
+			else if(*pEnd == '\\') // escape sequences
+			{
+				if(pEnd[1] == '"')
+					pEnd++;
+			}
+			else if(!InString)
+			{
+				if(*pEnd == ';')  // command separator
+				{
+					pNextPart = pEnd+1;
+					break;
+				}
+				else if(*pEnd == '#')  // comment, no need to do anything more
+					break;
+			}
+			
+			pEnd++;
+		}
+		
+		if(ParseStart(&Result, pStr, (pEnd-pStr) + 1) != 0)
+			return;
+
+		CCommand *pCommand = FindCommand(Result.m_pCommand);
+
+		if(pCommand)
+		{
+			int IsStrokeCommand = 0;
+			if(Result.m_pCommand[0] == '+')
+			{
+				// insert the stroke direction token
+				Result.AddArgument(aStrokeStr);
+				IsStrokeCommand = 1;
+			}
+			
+			if(Stroke || IsStrokeCommand)
+			{
+				if(ParseArgs(&Result, pCommand->m_pParams))
+				{
+					char aBuf[256];
+					str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams);
+					Print(aBuf);
+				}
+				else
+					pCommand->m_pfnCallback(&Result, pCommand->m_pUserData);
+			}
+		}
+		else
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand);
+			Print(aBuf);
+		}
+		
+		pStr = pNextPart;
+	}
+}
+
+void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser)
+{
+	CCommand *pCommand;
+	for(pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext)
+	{
+		if(pCommand->m_Flags&FlagMask)
+		{
+			if(str_find_nocase(pCommand->m_pName, pStr))
+				pfnCallback(pCommand->m_pName, pUser);
+		}
+	}	
+}
+
+// TODO: this should regard the commands flag
+CConsole::CCommand *CConsole::FindCommand(const char *pName)
+{
+	CCommand *pCommand;
+	for (pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext)
+	{
+		if(str_comp_nocase(pCommand->m_pName, pName) == 0)
+			return pCommand;
+	}	
+	
+	return 0x0;
+}
+
+void CConsole::ExecuteLine(const char *pStr)
+{
+	CConsole::ExecuteLineStroked(1, pStr);
+}
+
+
+void CConsole::ExecuteFile(const char *pFilename)
+{
+	// make sure that this isn't being executed already
+	for(CExecFile *pCur = m_pFirstExec; pCur; pCur = pCur->m_pPrev)
+		if(str_comp(pFilename, pCur->m_pFilename) == 0)
+			return;
+
+	if(!m_pStorage)
+		m_pStorage = Kernel()->RequestInterface<IStorage>();
+	if(!m_pStorage)
+		return;
+		
+	// push this one to the stack
+	CExecFile ThisFile;
+	CExecFile *pPrev = m_pFirstExec;
+	ThisFile.m_pFilename = pFilename;
+	ThisFile.m_pPrev = m_pFirstExec;
+	m_pFirstExec = &ThisFile;
+
+	// exec the file
+	IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ);
+	
+	if(File)
+	{
+		char *pLine;
+		CLineReader lr;
+		
+		dbg_msg("console", "executing '%s'", pFilename);
+		lr.Init(File);
+
+		while((pLine = lr.Get()))
+			ExecuteLine(pLine);
+
+		io_close(File);
+	}
+	else
+		dbg_msg("console", "failed to open '%s'", pFilename);
+	
+	m_pFirstExec = pPrev;
+}
+
+void CConsole::Con_Echo(IResult *pResult, void *pUserData)
+{
+	((CConsole*)pUserData)->Print(pResult->GetString(0));
+}
+
+void CConsole::Con_Exec(IResult *pResult, void *pUserData)
+{
+	((CConsole*)pUserData)->ExecuteFile(pResult->GetString(0));
+}
+
+struct CIntVariableData
+{
+	IConsole *m_pConsole;
+	int *m_pVariable;
+	int m_Min;
+	int m_Max;
+};
+
+struct CStrVariableData
+{
+	IConsole *m_pConsole;
+	char *m_pStr;
+	int m_MaxSize;
+};
+
+static void IntVariableCommand(IConsole::IResult *pResult, void *pUserData)
+{
+	CIntVariableData *pData = (CIntVariableData *)pUserData;
+
+	if(pResult->NumArguments())
+	{
+		int Val = pResult->GetInteger(0);
+		
+		// do clamping
+		if(pData->m_Min != pData->m_Max)
+		{
+			if (Val < pData->m_Min)
+				Val = pData->m_Min;
+			if (pData->m_Max != 0 && Val > pData->m_Max)
+				Val = pData->m_Max;
+		}
+
+		*(pData->m_pVariable) = Val;
+	}
+	else
+	{
+		char aBuf[1024];
+		str_format(aBuf, sizeof(aBuf), "Value: %d", *(pData->m_pVariable));
+		pData->m_pConsole->Print(aBuf);
+	}
+}
+
+static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData)
+{
+	CStrVariableData *pData = (CStrVariableData *)pUserData;
+
+	if(pResult->NumArguments())
+		str_copy(pData->m_pStr, pResult->GetString(0), pData->m_MaxSize);
+	else
+	{
+		char aBuf[1024];
+		str_format(aBuf, sizeof(aBuf), "Value: %s", pData->m_pStr);
+		pData->m_pConsole->Print(aBuf);
+	}
+}
+
+CConsole::CConsole()
+{
+	m_pFirstCommand = 0;
+	m_pFirstExec = 0;
+	m_pPrintCallbackUserdata = 0;
+	m_pfnPrintCallback = 0;
+	
+	m_pStorage = 0;
+	
+	// register some basic commands
+	Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text");
+	Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file");
+	
+	// TODO: this should disappear
+	#define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \
+	{ \
+		static CIntVariableData Data = { this, &g_Config.m_##Name, Min, Max }; \
+		Register(#ScriptName, "?i", Flags, IntVariableCommand, &Data, Desc); \
+	}
+	
+	#define MACRO_CONFIG_STR(Name,ScriptName,Len,Def,Flags,Desc) \
+	{ \
+		static CStrVariableData Data = { this, g_Config.m_##Name, Len }; \
+		Register(#ScriptName, "?r", Flags, StrVariableCommand, &Data, Desc); \
+	}
+
+	#include "config_variables.h" 
+
+	#undef MACRO_CONFIG_INT 
+	#undef MACRO_CONFIG_STR 	
+}
+
+void CConsole::ParseArguments(int NumArgs, const char **ppArguments)
+{
+	for(int i = 0; i < NumArgs; i++)
+	{
+		// check for scripts to execute
+		if(ppArguments[i][0] == '-' && ppArguments[i][1] == 'f' && ppArguments[i][2] == 0 && NumArgs - i > 1)
+		{
+			ExecuteFile(ppArguments[i+1]);
+			i++;
+		}
+		else
+		{
+			// search arguments for overrides
+			ExecuteLine(ppArguments[i]);
+		}
+	}
+}
+
+void CConsole::Register(const char *pName, const char *pParams, 
+	int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp)
+{
+	CCommand *pCommand = (CCommand *)mem_alloc(sizeof(CCommand), sizeof(void*));
+	pCommand->m_pfnCallback = pfnFunc;
+	pCommand->m_pUserData = pUser;
+	pCommand->m_pHelp = pHelp;
+	pCommand->m_pName = pName;
+	pCommand->m_pParams = pParams;
+	pCommand->m_Flags = Flags;
+	
+	
+	pCommand->m_pNext = m_pFirstCommand;
+	m_pFirstCommand = pCommand;
+}
+
+void CConsole::Con_Chain(IResult *pResult, void *pUserData)
+{
+	CChain *pInfo = (CChain *)pUserData;
+	pInfo->m_pfnChainCallback(pResult, pInfo->m_pUserData, pInfo->m_pfnCallback, pInfo->m_pCallbackUserData);
+}
+
+void CConsole::Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser)
+{
+	CCommand *pCommand = FindCommand(pName);
+	
+	if(!pCommand)
+	{
+		dbg_msg("console", "failed to chain '%s'", pName);
+		return;
+	}
+	
+	CChain *pChainInfo = (CChain *)mem_alloc(sizeof(CChain), sizeof(void*));
+
+	// store info
+	pChainInfo->m_pfnChainCallback = pfnChainFunc;
+	pChainInfo->m_pUserData = pUser;
+	pChainInfo->m_pfnCallback = pCommand->m_pfnCallback;
+	pChainInfo->m_pCallbackUserData = pCommand->m_pUserData;
+	
+	// chain
+	pCommand->m_pfnCallback = Con_Chain;
+	pCommand->m_pUserData = pChainInfo;
+}
+
+
+IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName)
+{
+	return FindCommand(pName);
+}
+
+
+extern IConsole *CreateConsole() { return new CConsole(); }
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
new file mode 100644
index 00000000..93d23547
--- /dev/null
+++ b/src/engine/shared/console.h
@@ -0,0 +1,96 @@
+#ifndef ENGINE_SHARED_CONSOLE_H
+#define ENGINE_SHARED_CONSOLE_H
+
+#include <engine/console.h>
+
+class CConsole : public IConsole
+{
+	class CCommand : public CCommandInfo
+	{
+	public:
+		CCommand *m_pNext;
+		int m_Flags;
+		FCommandCallback m_pfnCallback;
+		void *m_pUserData;
+	};
+		
+
+	class CChain
+	{
+	public:
+		FChainCommandCallback m_pfnChainCallback;
+		FCommandCallback m_pfnCallback;
+		void *m_pCallbackUserData;
+		void *m_pUserData;
+	};	
+	
+	CCommand *m_pFirstCommand;
+
+	class CExecFile
+	{
+	public:
+		const char *m_pFilename;
+		struct CExecFile *m_pPrev;
+	};
+	
+	CExecFile *m_pFirstExec;
+	class IStorage *m_pStorage;
+
+	static void Con_Chain(IResult *pResult, void *pUserData);
+	static void Con_Echo(IResult *pResult, void *pUserData);
+	static void Con_Exec(IResult *pResult, void *pUserData);
+
+	void ExecuteFileRecurse(const char *pFilename);
+	void ExecuteLineStroked(int Stroke, const char *pStr);
+	
+	FPrintCallback m_pfnPrintCallback;
+	void *m_pPrintCallbackUserdata;
+
+	enum
+	{
+		CONSOLE_MAX_STR_LENGTH  = 1024,
+		MAX_PARTS = (CONSOLE_MAX_STR_LENGTH+1)/2
+	};
+	
+	class CResult : public IResult
+	{
+	public:
+		char m_aStringStorage[CONSOLE_MAX_STR_LENGTH+1];
+		char *m_pArgsStart;
+		
+		const char *m_pCommand;
+		const char *m_apArgs[MAX_PARTS];
+		
+		void AddArgument(const char *pArg)
+		{
+			m_apArgs[m_NumArgs++] = pArg;
+		}
+
+		virtual const char *GetString(unsigned Index);
+		virtual int GetInteger(unsigned Index);
+		virtual float GetFloat(unsigned Index);
+	};
+	
+	int ParseStart(CResult *pResult, const char *pString, int Length);
+	int ParseArgs(CResult *pResult, const char *pFormat);
+
+	CCommand *FindCommand(const char *pName);
+
+public:
+	CConsole();
+
+	virtual CCommandInfo *GetCommandInfo(const char *pName);
+	virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) ;
+
+	virtual void ParseArguments(int NumArgs, const char **ppArguments);
+	virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp);
+	virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser);
+	
+	virtual void ExecuteLine(const char *pStr);
+	virtual void ExecuteFile(const char *pFilename);
+
+	virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData);
+	virtual void Print(const char *pStr);
+};
+
+#endif
diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp
new file mode 100644
index 00000000..dcc32ef2
--- /dev/null
+++ b/src/engine/shared/datafile.cpp
@@ -0,0 +1,643 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include <engine/storage.h>
+#include "datafile.h"
+#include "engine.h"
+#include <zlib.h>
+
+static const int DEBUG=0;
+
+struct CDatafileItemType
+{
+	int m_Type;
+	int m_Start;
+	int m_Num;
+} ;
+
+struct CDatafileItem
+{
+	int m_TypeAndId;
+	int m_Size;
+};
+
+struct CDatafileHeader
+{
+	char m_aId[4];
+	int m_Version;
+	int m_Size;
+	int m_Swaplen;
+	int m_NumItemTypes;
+	int m_NumItems;
+	int m_NumRawData;
+	int m_ItemSize;
+	int m_DataSize;
+};
+
+struct CDatafileData
+{
+	int m_NumItemTypes;
+	int m_NumItems;
+	int m_NumRawData;
+	int m_ItemSize;
+	int m_DataSize;
+	char m_aStart[4];
+};
+
+struct CDatafileInfo
+{
+	CDatafileItemType *m_pItemTypes;
+	int *m_pItemOffsets;
+	int *m_pDataOffsets;
+	int *m_pDataSizes;
+
+	char *m_pItemStart;
+	char *m_pDataStart;
+};
+
+struct CDatafile
+{
+	IOHANDLE m_File;
+	unsigned m_Crc;
+	CDatafileInfo m_Info;
+	CDatafileHeader m_Header;
+	int m_DataStartOffset;
+	char **m_ppDataPtrs;
+	char *m_pData;
+};
+
+bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename)
+{
+	dbg_msg("datafile", "loading. filename='%s'", pFilename);
+
+	IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ);
+	if(!File)
+	{
+		dbg_msg("datafile", "could not open '%s'", pFilename);
+		return false;
+	}	
+	
+	
+	// take the CRC of the file and store it
+	unsigned Crc = 0;
+	{
+		enum
+		{
+			BUFFER_SIZE = 64*1024
+		};
+		
+		unsigned char aBuffer[BUFFER_SIZE];
+		
+		while(1)
+		{
+			unsigned Bytes = io_read(File, aBuffer, BUFFER_SIZE);
+			if(Bytes <= 0)
+				break;
+			Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention
+		}
+		
+		io_seek(File, 0, IOSEEK_START);
+	}
+	
+	
+	// TODO: change this header
+	CDatafileHeader Header;
+	io_read(File, &Header, sizeof(Header));
+	if(Header.m_aId[0] != 'A' || Header.m_aId[1] != 'T' || Header.m_aId[2] != 'A' || Header.m_aId[3] != 'D')
+	{
+		if(Header.m_aId[0] != 'D' || Header.m_aId[1] != 'A' || Header.m_aId[2] != 'T' || Header.m_aId[3] != 'A')
+		{
+			dbg_msg("datafile", "wrong signature. %x %x %x %x", Header.m_aId[0], Header.m_aId[1], Header.m_aId[2], Header.m_aId[3]);
+			return 0;
+		}
+	}
+
+#if defined(CONF_ARCH_ENDIAN_BIG)
+	swap_endian(&Header, sizeof(int), sizeof(Header)/sizeof(int));	
+#endif
+	if(Header.m_Version != 3 && Header.m_Version != 4)
+	{
+		dbg_msg("datafile", "wrong version. version=%x", Header.m_Version);
+		return 0;
+	}
+	
+	// read in the rest except the data
+	unsigned Size = 0;
+	Size += Header.m_NumItemTypes*sizeof(CDatafileItemType);
+	Size += (Header.m_NumItems+Header.m_NumRawData)*sizeof(int);
+	if(Header.m_Version == 4)
+		Size += Header.m_NumRawData*sizeof(int); // v4 has uncompressed data sizes aswell
+	Size += Header.m_ItemSize;
+	
+	unsigned AllocSize = Size;
+	AllocSize += sizeof(CDatafile); // add space for info structure
+	AllocSize += Header.m_NumRawData*sizeof(void*); // add space for data pointers
+
+	m_pDataFile = (CDatafile*)mem_alloc(AllocSize, 1);
+	m_pDataFile->m_Header = Header;
+	m_pDataFile->m_DataStartOffset = sizeof(CDatafileHeader) + Size;
+	m_pDataFile->m_ppDataPtrs = (char**)(m_pDataFile+1);
+	m_pDataFile->m_pData = (char *)(m_pDataFile+1)+Header.m_NumRawData*sizeof(char *);
+	m_pDataFile->m_File = File;
+	m_pDataFile->m_Crc = Crc;
+	
+	// clear the data pointers
+	mem_zero(m_pDataFile->m_ppDataPtrs, Header.m_NumRawData*sizeof(void*));
+	
+	// read types, offsets, sizes and item data
+	unsigned ReadSize = io_read(File, m_pDataFile->m_pData, Size);
+	if(ReadSize != Size)
+	{
+		mem_free(m_pDataFile);
+		m_pDataFile = 0;
+		dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", Size, ReadSize);
+		return false;
+	}
+
+#if defined(CONF_ARCH_ENDIAN_BIG)
+	swap_endian(m_pDataFile->m_pData, sizeof(int), Header.Swaplen / sizeof(int));
+#endif
+
+	//if(DEBUG)
+	{
+		dbg_msg("datafile", "allocsize=%d", AllocSize);
+		dbg_msg("datafile", "readsize=%d", ReadSize);
+		dbg_msg("datafile", "swaplen=%d", Header.m_Swaplen);
+		dbg_msg("datafile", "item_size=%d", m_pDataFile->m_Header.m_ItemSize);
+	}
+	
+	m_pDataFile->m_Info.m_pItemTypes = (CDatafileItemType *)m_pDataFile->m_pData;
+	m_pDataFile->m_Info.m_pItemOffsets = (int *)&m_pDataFile->m_Info.m_pItemTypes[m_pDataFile->m_Header.m_NumItemTypes];
+	m_pDataFile->m_Info.m_pDataOffsets = (int *)&m_pDataFile->m_Info.m_pItemOffsets[m_pDataFile->m_Header.m_NumItems];
+	m_pDataFile->m_Info.m_pDataSizes = (int *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData];
+	
+	if(Header.m_Version == 4)
+		m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataSizes[m_pDataFile->m_Header.m_NumRawData];
+	else
+		m_pDataFile->m_Info.m_pItemStart = (char *)&m_pDataFile->m_Info.m_pDataOffsets[m_pDataFile->m_Header.m_NumRawData];
+	m_pDataFile->m_Info.m_pDataStart = m_pDataFile->m_Info.m_pItemStart + m_pDataFile->m_Header.m_ItemSize;
+
+	dbg_msg("datafile", "loading done. datafile='%s'", pFilename);
+
+	if(DEBUG)
+	{
+		/*
+		for(int i = 0; i < m_pDataFile->data.num_raw_data; i++)
+		{
+			void *p = datafile_get_data(df, i);
+			dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&m_pDataFile->data)), size);
+		}
+			
+		for(int i = 0; i < datafile_num_items(df); i++)
+		{
+			int type, id;
+			void *data = datafile_get_item(df, i, &type, &id);
+			dbg_msg("map", "\t%d: type=%x id=%x p=%p offset=%d", i, type, id, data, m_pDataFile->info.item_offsets[i]);
+			int *idata = (int*)data;
+			for(int k = 0; k < 3; k++)
+				dbg_msg("datafile", "\t\t%d=%d (%x)", k, idata[k], idata[k]);
+		}
+
+		for(int i = 0; i < m_pDataFile->data.num_m_aItemTypes; i++)
+		{
+			dbg_msg("map", "\t%d: type=%x start=%d num=%d", i,
+				m_pDataFile->info.m_aItemTypes[i].type,
+				m_pDataFile->info.m_aItemTypes[i].start,
+				m_pDataFile->info.m_aItemTypes[i].num);
+			for(int k = 0; k < m_pDataFile->info.m_aItemTypes[i].num; k++)
+			{
+				int type, id;
+				datafile_get_item(df, m_pDataFile->info.m_aItemTypes[i].start+k, &type, &id);
+				if(type != m_pDataFile->info.m_aItemTypes[i].type)
+					dbg_msg("map", "\tERROR");
+			}
+		}
+		*/
+	}
+		
+	return true;
+}
+
+int CDataFileReader::NumData()
+{
+	if(!m_pDataFile) { return 0; }
+	return m_pDataFile->m_Header.m_NumRawData;
+}
+
+// always returns the size in the file
+int CDataFileReader::GetDataSize(int Index)
+{
+	if(!m_pDataFile) { return 0; }
+	
+	if(Index == m_pDataFile->m_Header.m_NumRawData-1)
+		return m_pDataFile->m_Header.m_DataSize-m_pDataFile->m_Info.m_pDataOffsets[Index];
+	return  m_pDataFile->m_Info.m_pDataOffsets[Index+1]-m_pDataFile->m_Info.m_pDataOffsets[Index];
+}
+
+void *CDataFileReader::GetDataImpl(int Index, int Swap)
+{
+	if(!m_pDataFile) { return 0; }
+	
+	// load it if needed
+	if(!m_pDataFile->m_ppDataPtrs[Index])
+	{
+		// fetch the data size
+		int DataSize = GetDataSize(Index);
+		int SwapSize = DataSize;
+		
+		if(m_pDataFile->m_Header.m_Version == 4)
+		{
+			// v4 has compressed data
+			void *pTemp = (char *)mem_alloc(DataSize, 1);
+			unsigned long UncompressedSize = m_pDataFile->m_Info.m_pDataSizes[Index];
+			unsigned long s;
+
+			dbg_msg("datafile", "loading data index=%d size=%d uncompressed=%d", Index, DataSize, UncompressedSize);
+			m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(UncompressedSize, 1);
+			
+			// read the compressed data
+			io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START);
+			io_read(m_pDataFile->m_File, pTemp, DataSize);
+
+			// decompress the data, TODO: check for errors
+			s = UncompressedSize;
+			uncompress((Bytef*)m_pDataFile->m_ppDataPtrs[Index], &s, (Bytef*)pTemp, DataSize); // ignore_convention
+			SwapSize = s;
+
+			// clean up the temporary buffers
+			mem_free(pTemp);
+		}
+		else
+		{
+			// load the data
+			dbg_msg("datafile", "loading data index=%d size=%d", Index, DataSize);
+			m_pDataFile->m_ppDataPtrs[Index] = (char *)mem_alloc(DataSize, 1);
+			io_seek(m_pDataFile->m_File, m_pDataFile->m_DataStartOffset+m_pDataFile->m_Info.m_pDataOffsets[Index], IOSEEK_START);
+			io_read(m_pDataFile->m_File, m_pDataFile->m_ppDataPtrs[Index], DataSize);
+		}
+
+#if defined(CONF_ARCH_ENDIAN_BIG)
+		if(Swap && SwapSize)
+			swap_endian(m_pDataFile->m_ppDataPtrs[Index], sizeof(int), SwapSize/sizeof(int));
+#endif
+	}
+	
+	return m_pDataFile->m_ppDataPtrs[Index];
+}
+
+void *CDataFileReader::GetData(int Index)
+{
+	return GetDataImpl(Index, 0);
+}
+
+void *CDataFileReader::GetDataSwapped(int Index)
+{
+	return GetDataImpl(Index, 1);
+}
+
+void CDataFileReader::UnloadData(int Index)
+{
+	if(Index < 0)
+		return;
+		
+	//
+	mem_free(m_pDataFile->m_ppDataPtrs[Index]);
+	m_pDataFile->m_ppDataPtrs[Index] = 0x0;
+}
+
+int CDataFileReader::GetItemSize(int Index)
+{
+	if(!m_pDataFile) { return 0; }
+	if(Index == m_pDataFile->m_Header.m_NumItems-1)
+		return m_pDataFile->m_Header.m_ItemSize-m_pDataFile->m_Info.m_pItemOffsets[Index];
+	return  m_pDataFile->m_Info.m_pItemOffsets[Index+1]-m_pDataFile->m_Info.m_pItemOffsets[Index];
+}
+
+void *CDataFileReader::GetItem(int Index, int *pType, int *pId)
+{
+	if(!m_pDataFile) { if(pType) *pType = 0; if(pId) *pId = 0; return 0; }
+	
+	CDatafileItem *i = (CDatafileItem *)(m_pDataFile->m_Info.m_pItemStart+m_pDataFile->m_Info.m_pItemOffsets[Index]);
+	if(pType)
+		*pType = (i->m_TypeAndId>>16)&0xffff; // remove sign extention
+	if(pId)
+		*pId = i->m_TypeAndId&0xffff;
+	return (void *)(i+1);
+}
+
+void CDataFileReader::GetType(int Type, int *pStart, int *pNum)
+{
+	*pStart = 0;
+	*pNum = 0;
+
+	if(!m_pDataFile)
+		return;
+	
+	for(int i = 0; i < m_pDataFile->m_Header.m_NumItemTypes; i++)
+	{
+		if(m_pDataFile->m_Info.m_pItemTypes[i].m_Type == Type)
+		{
+			*pStart = m_pDataFile->m_Info.m_pItemTypes[i].m_Start;
+			*pNum = m_pDataFile->m_Info.m_pItemTypes[i].m_Num;
+			return;
+		}
+	}
+}
+
+void *CDataFileReader::FindItem(int Type, int Id)
+{
+	if(!m_pDataFile) return 0;
+	
+	int Start, Num;
+	GetType(Type, &Start, &Num);
+	for(int i = 0; i < Num; i++)
+	{
+		int ItemId;
+		void *pItem = GetItem(Start+i,0, &ItemId);
+		if(Id == ItemId)
+			return pItem;
+	}
+	return 0;
+}
+
+int CDataFileReader::NumItems()
+{
+	if(!m_pDataFile) return 0;
+	return m_pDataFile->m_Header.m_NumItems;
+}
+
+bool CDataFileReader::Close()
+{
+	if(!m_pDataFile)
+		return true;
+	
+	// free the data that is loaded
+	int i;
+	for(i = 0; i < m_pDataFile->m_Header.m_NumRawData; i++)
+		mem_free(m_pDataFile->m_ppDataPtrs[i]);
+	
+	io_close(m_pDataFile->m_File);
+	mem_free(m_pDataFile);
+	m_pDataFile = 0;
+	return true;
+}
+
+unsigned CDataFileReader::Crc()
+{
+	if(!m_pDataFile) return -1;
+	return m_pDataFile->m_Crc;
+}
+
+bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename)
+{
+	int i;
+	//DATAFILE_OUT *df = (DATAFILE_OUT*)mem_alloc(sizeof(DATAFILE_OUT), 1);
+	m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE);
+	if(!m_File)
+		return false;
+	
+	m_NumItems = 0;
+	m_NumDatas = 0;
+	m_NumItemTypes = 0;
+	mem_zero(&m_aItemTypes, sizeof(m_aItemTypes));
+
+	for(i = 0; i < 0xffff; i++)
+	{
+		m_aItemTypes[i].m_First = -1;
+		m_aItemTypes[i].m_Last = -1;
+	}
+	
+	return true;
+}
+
+int CDataFileWriter::AddItem(int Type, int Id, int Size, void *pData)
+{
+	m_aItems[m_NumItems].m_Type = Type;
+	m_aItems[m_NumItems].m_Id = Id;
+	m_aItems[m_NumItems].m_Size = Size;
+	
+	/*
+	dbg_msg("datafile", "added item type=%d id=%d size=%d", type, id, size);
+	int i;
+	for(i = 0; i < size/4; i++)
+		dbg_msg("datafile", "\t%d: %08x %d", i, ((int*)data)[i], ((int*)data)[i]);
+	*/
+	
+	// copy data
+	m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1);
+	mem_copy(m_aItems[m_NumItems].m_pData, pData, Size);
+
+	if(!m_aItemTypes[Type].m_Num) // count item types
+		m_NumItemTypes++;
+
+	// link
+	m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last;
+	m_aItems[m_NumItems].m_Next = -1;
+	
+	if(m_aItemTypes[Type].m_Last != -1)
+		m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems;
+	m_aItemTypes[Type].m_Last = m_NumItems;
+	
+	if(m_aItemTypes[Type].m_First == -1)
+		m_aItemTypes[Type].m_First = m_NumItems;
+	
+	m_aItemTypes[Type].m_Num++;
+		
+	m_NumItems++;
+	return m_NumItems-1;
+}
+
+int CDataFileWriter::AddData(int Size, void *pData)
+{
+	CDataInfo *pInfo = &m_aDatas[m_NumDatas];
+	unsigned long s = compressBound(Size);
+	void *pCompData = mem_alloc(s, 1); // temporary buffer that we use duing compression
+
+	int Result = compress((Bytef*)pCompData, &s, (Bytef*)pData, Size); // ignore_convention
+	if(Result != Z_OK)
+	{
+		dbg_msg("datafile", "compression error %d", Result);
+		dbg_assert(0, "zlib error");
+	}
+		
+	pInfo->m_UncompressedSize = Size;
+	pInfo->m_CompressedSize = (int)s;
+	pInfo->m_pCompressedData = mem_alloc(pInfo->m_CompressedSize, 1);
+	mem_copy(pInfo->m_pCompressedData, pCompData, pInfo->m_CompressedSize);
+	mem_free(pCompData);
+
+	m_NumDatas++;
+	return m_NumDatas-1;
+}
+
+int CDataFileWriter::AddDataSwapped(int Size, void *pData)
+{
+#if defined(CONF_ARCH_ENDIAN_BIG)
+	void *pSwapped = mem_alloc(Size, 1); // temporary buffer that we use duing compression
+	int Index;
+	mem_copy(pSwapped, pData, Size);
+	swap_endian(&pSwapped, sizeof(int), Size/sizeof(int));
+	Index = AddData(Size, Swapped);
+	mem_free(pSwapped);
+	return Index;
+#else
+	return AddData(Size, pData);
+#endif
+}
+
+
+int CDataFileWriter::Finish()
+{
+	int ItemSize = 0;
+	int TypesSize, HeaderSize, OffsetSize, FileSize, SwapSize;
+	int DataSize = 0;
+	CDatafileHeader Header;
+
+	// we should now write this file!
+	if(DEBUG)
+		dbg_msg("datafile", "writing");
+
+	// calculate sizes
+	for(int i = 0; i < m_NumItems; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem));
+		ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem);
+	}
+	
+	
+	for(int i = 0; i < m_NumDatas; i++)
+		DataSize += m_aDatas[i].m_CompressedSize;
+	
+	// calculate the complete size
+	TypesSize = m_NumItemTypes*sizeof(CDatafileItemType);
+	HeaderSize = sizeof(CDatafileHeader);
+	OffsetSize = m_NumItems*sizeof(int) + m_NumDatas*sizeof(int);
+	FileSize = HeaderSize + TypesSize + OffsetSize + ItemSize + DataSize;
+	SwapSize = FileSize - DataSize;
+	
+	(void)SwapSize;
+	
+	if(DEBUG)
+		dbg_msg("datafile", "num_m_aItemTypes=%d TypesSize=%d m_aItemsize=%d DataSize=%d", m_NumItemTypes, TypesSize, ItemSize, DataSize);
+	
+	// construct Header
+	{
+		Header.m_aId[0] = 'D';
+		Header.m_aId[1] = 'A';
+		Header.m_aId[2] = 'T';
+		Header.m_aId[3] = 'A';
+		Header.m_Version = 4;
+		Header.m_Size = FileSize - 16;
+		Header.m_Swaplen = SwapSize - 16;
+		Header.m_NumItemTypes = m_NumItemTypes;
+		Header.m_NumItems = m_NumItems;
+		Header.m_NumRawData = m_NumDatas;
+		Header.m_ItemSize = ItemSize;
+		Header.m_DataSize = DataSize;
+		
+		// TODO: apply swapping
+		// write Header
+		if(DEBUG)
+			dbg_msg("datafile", "HeaderSize=%d", sizeof(Header));
+		io_write(m_File, &Header, sizeof(Header));
+	}
+	
+	// write types
+	for(int i = 0, Count = 0; i < 0xffff; i++)
+	{
+		if(m_aItemTypes[i].m_Num)
+		{
+			// write info
+			CDatafileItemType Info;
+			Info.m_Type = i;
+			Info.m_Start = Count;
+			Info.m_Num = m_aItemTypes[i].m_Num;
+			if(DEBUG)
+				dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num);
+			io_write(m_File, &Info, sizeof(Info));
+			Count += m_aItemTypes[i].m_Num;
+		}
+	}
+	
+	// write item offsets
+	for(int i = 0, Offset = 0; i < 0xffff; i++)
+	{
+		if(m_aItemTypes[i].m_Num)
+		{
+			// write all m_aItems in of this type
+			int k = m_aItemTypes[i].m_First;
+			while(k != -1)
+			{
+				if(DEBUG)
+					dbg_msg("datafile", "writing item offset num=%d offset=%d", k, Offset);
+				io_write(m_File, &Offset, sizeof(Offset));
+				Offset += m_aItems[k].m_Size + sizeof(CDatafileItem);
+				
+				// next
+				k = m_aItems[k].m_Next;
+			}
+		}
+	}
+	
+	// write data offsets
+	for(int i = 0, Offset = 0; i < m_NumDatas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data offset num=%d offset=%d", i, Offset);
+		io_write(m_File, &Offset, sizeof(Offset));
+		Offset += m_aDatas[i].m_CompressedSize;
+	}
+
+	// write data uncompressed sizes
+	for(int i = 0; i < m_NumDatas; i++)
+	{
+		/*
+		if(DEBUG)
+			dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset);
+		*/
+		io_write(m_File, &m_aDatas[i].m_UncompressedSize, sizeof(int));
+	}
+	
+	// write m_aItems
+	for(int i = 0; i < 0xffff; i++)
+	{
+		if(m_aItemTypes[i].m_Num)
+		{
+			// write all m_aItems in of this type
+			int k = m_aItemTypes[i].m_First;
+			while(k != -1)
+			{
+				CDatafileItem Item;
+				Item.m_TypeAndId = (i<<16)|m_aItems[k].m_Id;
+				Item.m_Size = m_aItems[k].m_Size;
+				if(DEBUG)
+					dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_Id, m_aItems[k].m_Size);
+				
+				io_write(m_File, &Item, sizeof(Item));
+				io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size);
+				
+				// next
+				k = m_aItems[k].m_Next;
+			}
+		}
+	}
+	
+	// write data
+	for(int i = 0; i < m_NumDatas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize);
+		io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize);
+	}
+
+	// free data
+	for(int i = 0; i < m_NumItems; i++)
+		mem_free(m_aItems[i].m_pData);
+
+	
+	io_close(m_File);
+	
+	if(DEBUG)
+		dbg_msg("datafile", "done");
+	return 0;
+}
diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h
new file mode 100644
index 00000000..eddce611
--- /dev/null
+++ b/src/engine/shared/datafile.h
@@ -0,0 +1,77 @@
+#ifndef ENGINE_SHARED_DATAFILE_H
+#define ENGINE_SHARED_DATAFILE_H
+
+// raw datafile access
+class CDataFileReader
+{
+	class CDatafile *m_pDataFile;
+	void *GetDataImpl(int Index, int Swap);
+public:
+	CDataFileReader() : m_pDataFile(0) {}
+	~CDataFileReader() { Close(); }
+	
+	bool IsOpen() const { return m_pDataFile != 0; }
+	
+	bool Open(class IStorage *pStorage, const char *pFilename);
+	bool Close();
+	
+	void *GetData(int Index);
+	void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved
+	int GetDataSize(int Index);
+	void UnloadData(int Index);
+	void *GetItem(int Index, int *pType, int *pId);
+	int GetItemSize(int Index);
+	void GetType(int Type, int *pStart, int *pNum);
+	void *FindItem(int Type, int Id);
+	int NumItems();
+	int NumData();
+	void Unload();
+	
+	unsigned Crc();
+};
+
+// write access
+class CDataFileWriter
+{
+	struct CDataInfo
+	{
+		int m_UncompressedSize;
+		int m_CompressedSize;
+		void *m_pCompressedData;
+	} ;
+
+	struct CItemInfo
+	{
+		int m_Type;
+		int m_Id;
+		int m_Size;
+		int m_Next;
+		int m_Prev;
+		void *m_pData;
+	};
+
+	struct CItemTypeInfo
+	{
+		int m_Num;
+		int m_First;
+		int m_Last;
+	};
+	
+	IOHANDLE m_File;
+	int m_NumItems;
+	int m_NumDatas;
+	int m_NumItemTypes;
+	CItemTypeInfo m_aItemTypes[0xffff];
+	CItemInfo m_aItems[1024];
+	CDataInfo m_aDatas[1024];	
+	
+public:
+	bool Open(class IStorage *pStorage, const char *Filename);
+	int AddData(int Size, void *pData);
+	int AddDataSwapped(int Size, void *pData);
+	int AddItem(int Type, int Id, int Size, void *pData);
+	int Finish();
+};
+
+
+#endif
diff --git a/src/engine/shared/demorec.cpp b/src/engine/shared/demorec.cpp
new file mode 100644
index 00000000..48b06e9a
--- /dev/null
+++ b/src/engine/shared/demorec.cpp
@@ -0,0 +1,623 @@
+#include <base/system.h>
+#include <engine/shared/protocol.h>
+#include <engine/storage.h>
+#include "demorec.h"
+#include "memheap.h"
+#include "snapshot.h"
+#include "compression.h"
+#include "network.h"
+#include "engine.h"
+
+static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1};
+
+CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta)
+{
+	m_File = 0;
+	m_LastTickMarker = -1;
+	m_pSnapshotDelta = pSnapshotDelta;
+}
+
+//static IOHANDLE m_File = 0;
+
+// Record
+int CDemoRecorder::Start(class IStorage *pStorage, const char *pFilename, const char *pNetVersion, const char *pMap, int Crc, const char *pType)
+{
+	CDemoHeader Header;
+	if(m_File)
+		return -1;
+
+	m_File = pStorage->OpenFile(pFilename, IOFLAG_WRITE);
+	
+	if(!m_File)
+	{
+		dbg_msg("demorec/record", "Unable to open '%s' for recording", pFilename);
+		return -1;
+	}
+	
+	// write header
+	mem_zero(&Header, sizeof(Header));
+	mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker));
+	str_copy(Header.m_aNetversion, pNetVersion, sizeof(Header.m_aNetversion));
+	str_copy(Header.m_aMap, pMap, sizeof(Header.m_aMap));
+	str_copy(Header.m_aType, pType, sizeof(Header.m_aType));
+	Header.m_aCrc[0] = (Crc>>24)&0xff;
+	Header.m_aCrc[1] = (Crc>>16)&0xff;
+	Header.m_aCrc[2] = (Crc>>8)&0xff;
+	Header.m_aCrc[3] = (Crc)&0xff;
+	io_write(m_File, &Header, sizeof(Header));
+	
+	m_LastKeyFrame = -1;
+	m_LastTickMarker = -1;
+	
+	dbg_msg("demorec/record", "Recording to '%s'", pFilename);
+	return 0;
+}
+
+/*
+	Tickmarker
+		7   = Always set
+		6   = Keyframe flag
+		0-5 = Delta tick
+	
+	Normal
+		7   = Not set
+		5-6 = Type
+		0-4 = Size
+*/
+
+enum
+{
+	CHUNKTYPEFLAG_TICKMARKER = 0x80,
+	CHUNKTICKFLAG_KEYFRAME = 0x40, // only when tickmarker is set
+	
+	CHUNKMASK_TICK = 0x3f,
+	CHUNKMASK_TYPE = 0x60,
+	CHUNKMASK_SIZE = 0x1f,
+	
+	CHUNKTYPE_SNAPSHOT = 1,
+	CHUNKTYPE_MESSAGE = 2,
+	CHUNKTYPE_DELTA = 3,
+
+	CHUNKFLAG_BIGSIZE = 0x10
+};
+
+void CDemoRecorder::WriteTickMarker(int Tick, int Keyframe)
+{
+	if(m_LastTickMarker == -1 || Tick-m_LastTickMarker > 63 || Keyframe)
+	{
+		unsigned char aChunk[5];
+		aChunk[0] = CHUNKTYPEFLAG_TICKMARKER;
+		aChunk[1] = (Tick>>24)&0xff;
+		aChunk[2] = (Tick>>16)&0xff;
+		aChunk[3] = (Tick>>8)&0xff;
+		aChunk[4] = (Tick)&0xff;
+
+		if(Keyframe)
+			aChunk[0] |= CHUNKTICKFLAG_KEYFRAME;
+		
+		io_write(m_File, aChunk, sizeof(aChunk));
+	}
+	else
+	{
+		unsigned char aChunk[1];
+		aChunk[0] = CHUNKTYPEFLAG_TICKMARKER | (Tick-m_LastTickMarker);
+		io_write(m_File, aChunk, sizeof(aChunk));
+	}	
+
+	m_LastTickMarker = Tick;
+}
+
+void CDemoRecorder::Write(int Type, const void *pData, int Size)
+{
+	char aBuffer[64*1024];
+	char aBuffer2[64*1024];
+	unsigned char aChunk[3];
+	
+	if(!m_File)
+		return;
+
+	/* pad the data with 0 so we get an alignment of 4,
+	else the compression won't work and miss some bytes */
+	mem_copy(aBuffer2, pData, Size);
+	while(Size&3)
+		aBuffer2[Size++] = 0;
+	Size = CVariableInt::Compress(aBuffer2, Size, aBuffer); // buffer2 -> buffer
+	Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2)); // buffer -> buffer2
+	
+	
+	aChunk[0] = ((Type&0x3)<<5);
+	if(Size < 30)
+	{
+		aChunk[0] |= Size;
+		io_write(m_File, aChunk, 1);
+	}
+	else
+	{
+		if(Size < 256)
+		{
+			aChunk[0] |= 30;
+			aChunk[1] = Size&0xff;
+			io_write(m_File, aChunk, 2);
+		}
+		else
+		{
+			aChunk[0] |= 31;
+			aChunk[1] = Size&0xff;
+			aChunk[2] = Size>>8;
+			io_write(m_File, aChunk, 3);
+		}
+	}
+	
+	io_write(m_File, aBuffer2, Size);
+}
+
+void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size)
+{
+	if(m_LastKeyFrame == -1 || (Tick-m_LastKeyFrame) > SERVER_TICK_SPEED*5)
+	{
+		// write full tickmarker
+		WriteTickMarker(Tick, 1);
+		
+		// write snapshot
+		Write(CHUNKTYPE_SNAPSHOT, pData, Size);
+			
+		m_LastKeyFrame = Tick;
+		mem_copy(m_aLastSnapshotData, pData, Size);
+	}
+	else
+	{
+		// create delta, prepend tick
+		char aDeltaData[CSnapshot::MAX_SIZE+sizeof(int)];
+		int DeltaSize;
+
+		// write tickmarker
+		WriteTickMarker(Tick, 0);
+		
+		DeltaSize = m_pSnapshotDelta->CreateDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)pData, &aDeltaData);
+		if(DeltaSize)
+		{
+			// record delta
+			Write(CHUNKTYPE_DELTA, aDeltaData, DeltaSize);
+			mem_copy(m_aLastSnapshotData, pData, Size);
+		}
+	}
+}
+
+void CDemoRecorder::RecordMessage(const void *pData, int Size)
+{
+	Write(CHUNKTYPE_MESSAGE, pData, Size);
+}
+
+int CDemoRecorder::Stop()
+{
+	if(!m_File)
+		return -1;
+		
+	dbg_msg("demorec/record", "Stopped recording");
+	io_close(m_File);
+	m_File = 0;
+	return 0;
+}
+
+
+
+CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta)
+{
+	m_File = 0;
+	m_pKeyFrames = 0;
+
+	m_pSnapshotDelta = pSnapshotDelta;
+	m_LastSnapshotDataSize = -1;
+}
+
+void CDemoPlayer::SetListner(IListner *pListner)
+{
+	m_pListner = pListner;
+}
+
+
+int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
+{
+	unsigned char Chunk = 0;
+	
+	*pSize = 0;
+	*pType = 0;
+	
+	if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk))
+		return -1;
+		
+	if(Chunk&CHUNKTYPEFLAG_TICKMARKER)
+	{
+		// decode tick marker
+		int Tickdelta = Chunk&(CHUNKMASK_TICK);
+		*pType = Chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME);
+		
+		if(Tickdelta == 0)
+		{
+			unsigned char aTickdata[4];
+			if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
+				return -1;
+			*pTick = (aTickdata[0]<<24) | (aTickdata[1]<<16) | (aTickdata[2]<<8) | aTickdata[3];
+		}
+		else
+		{
+			*pTick += Tickdelta;
+		}
+		
+	}
+	else
+	{
+		// decode normal chunk
+		*pType = (Chunk&CHUNKMASK_TYPE)>>5;
+		*pSize = Chunk&CHUNKMASK_SIZE;
+		
+		if(*pSize == 30)
+		{
+			unsigned char aSizedata[1];
+			if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
+				return -1;
+			*pSize = aSizedata[0];
+			
+		}
+		else if(*pSize == 31)
+		{
+			unsigned char aSizedata[2];
+			if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
+				return -1;
+			*pSize = (aSizedata[1]<<8) | aSizedata[0];
+		}
+	}
+	
+	return 0;
+}
+
+void CDemoPlayer::ScanFile()
+{
+	long StartPos;
+	CHeap Heap;
+	CKeyFrameSearch *pFirstKey = 0;
+	CKeyFrameSearch *pCurrentKey = 0;
+	//DEMOREC_CHUNK chunk;
+	int ChunkSize, ChunkType, ChunkTick = 0;
+	int i;
+
+	StartPos = io_tell(m_File);
+	m_Info.m_SeekablePoints = 0;
+
+	while(1)
+	{
+		long CurrentPos = io_tell(m_File);
+		
+		if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
+			break;
+			
+		// read the chunk
+		if(ChunkType&CHUNKTYPEFLAG_TICKMARKER)
+		{
+			if(ChunkType&CHUNKTICKFLAG_KEYFRAME)
+			{
+				CKeyFrameSearch *pKey;
+				
+				// save the position
+				pKey = (CKeyFrameSearch *)Heap.Allocate(sizeof(CKeyFrameSearch));
+				pKey->m_Frame.m_Filepos = CurrentPos;
+				pKey->m_Frame.m_Tick = ChunkTick;
+				pKey->m_pNext = 0;
+				if(pCurrentKey)
+					pCurrentKey->m_pNext = pKey;
+				if(!pFirstKey)
+					pFirstKey = pKey;
+				pCurrentKey = pKey;
+				m_Info.m_SeekablePoints++;
+			}
+			
+			if(m_Info.m_Info.m_FirstTick == -1)
+				m_Info.m_Info.m_FirstTick = ChunkTick;
+			m_Info.m_Info.m_LastTick = ChunkTick;
+		}
+		else if(ChunkSize)
+			io_skip(m_File, ChunkSize);
+			
+	}
+
+	// copy all the frames to an array instead for fast access
+	m_pKeyFrames = (CKeyFrame*)mem_alloc(m_Info.m_SeekablePoints*sizeof(CKeyFrame), 1);
+	for(pCurrentKey = pFirstKey, i = 0; pCurrentKey; pCurrentKey = pCurrentKey->m_pNext, i++)
+		m_pKeyFrames[i] = pCurrentKey->m_Frame;
+		
+	// destroy the temporary heap and seek back to the start
+	io_seek(m_File, StartPos, IOSEEK_START);
+}
+
+void CDemoPlayer::DoTick()
+{
+	static char aCompresseddata[CSnapshot::MAX_SIZE];
+	static char aDecompressed[CSnapshot::MAX_SIZE];
+	static char aData[CSnapshot::MAX_SIZE];
+	int ChunkType, ChunkTick, ChunkSize;
+	int DataSize;
+	int GotSnapshot = 0;
+
+	// update ticks
+	m_Info.m_PreviousTick = m_Info.m_Info.m_CurrentTick;
+	m_Info.m_Info.m_CurrentTick = m_Info.m_NextTick;
+	ChunkTick = m_Info.m_Info.m_CurrentTick;
+
+	while(1)
+	{
+		if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
+		{
+			// stop on error or eof
+			dbg_msg("demorec", "end of file");
+			Pause();
+			break;
+		}
+		
+		// read the chunk
+		if(ChunkSize)
+		{
+			if(io_read(m_File, aCompresseddata, ChunkSize) != (unsigned)ChunkSize)
+			{
+				// stop on error or eof
+				dbg_msg("demorec", "error reading chunk");
+				Stop();
+				break;
+			}
+			
+			DataSize = CNetBase::Decompress(aCompresseddata, ChunkSize, aDecompressed, sizeof(aDecompressed));
+			if(DataSize < 0)
+			{
+				// stop on error or eof
+				dbg_msg("demorec", "error during network decompression");
+				Stop();
+				break;
+			}
+			
+			DataSize = CVariableInt::Decompress(aDecompressed, DataSize, aData);
+
+			if(DataSize < 0)
+			{
+				dbg_msg("demorec", "error during intpack decompression");
+				Stop();
+				break;
+			}
+		}
+			
+		if(ChunkType == CHUNKTYPE_DELTA)
+		{
+			// process delta snapshot
+			static char aNewsnap[CSnapshot::MAX_SIZE];
+			
+			GotSnapshot = 1;
+			
+			DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot*)m_aLastSnapshotData, (CSnapshot*)aNewsnap, aData, DataSize);
+			
+			if(DataSize >= 0)
+			{
+				if(m_pListner)
+					m_pListner->OnDemoPlayerSnapshot(aNewsnap, DataSize);
+
+				m_LastSnapshotDataSize = DataSize;
+				mem_copy(m_aLastSnapshotData, aNewsnap, DataSize);
+			}
+			else
+				dbg_msg("demorec", "error duing unpacking of delta, err=%d", DataSize);
+		}
+		else if(ChunkType == CHUNKTYPE_SNAPSHOT)
+		{
+			// process full snapshot
+			GotSnapshot = 1;
+			
+			m_LastSnapshotDataSize = DataSize;
+			mem_copy(m_aLastSnapshotData, aData, DataSize);
+			if(m_pListner)
+				m_pListner->OnDemoPlayerSnapshot(aData, DataSize);
+		}
+		else
+		{
+			// if there were no snapshots in this tick, replay the last one
+			if(!GotSnapshot && m_pListner && m_LastSnapshotDataSize != -1)
+			{
+				GotSnapshot = 1;
+				m_pListner->OnDemoPlayerSnapshot(m_aLastSnapshotData, m_LastSnapshotDataSize);
+			}
+			
+			// check the remaining types
+			if(ChunkType&CHUNKTYPEFLAG_TICKMARKER)
+			{
+				m_Info.m_NextTick = ChunkTick;
+				break;
+			}
+			else if(ChunkType == CHUNKTYPE_MESSAGE)
+			{
+				if(m_pListner)
+					m_pListner->OnDemoPlayerMessage(aData, DataSize);
+			}
+		}
+	}
+}
+
+void CDemoPlayer::Pause()
+{
+	m_Info.m_Info.m_Paused = 1;
+}
+
+void CDemoPlayer::Unpause()
+{
+	if(m_Info.m_Info.m_Paused)
+	{
+		/*m_Info.start_tick = m_Info.current_tick;
+		m_Info.start_time = time_get();*/
+		m_Info.m_Info.m_Paused = 0;
+	}
+}
+
+int CDemoPlayer::Load(class IStorage *pStorage, const char *pFilename)
+{
+	m_File = pStorage->OpenFile(pFilename, IOFLAG_READ);
+	if(!m_File)
+	{
+		dbg_msg("demorec/playback", "could not open '%s'", pFilename);
+		return -1;
+	}
+	
+	// clear the playback info
+	mem_zero(&m_Info, sizeof(m_Info));
+	m_Info.m_Info.m_FirstTick = -1;
+	m_Info.m_Info.m_LastTick = -1;
+	//m_Info.start_tick = -1;
+	m_Info.m_NextTick = -1;
+	m_Info.m_Info.m_CurrentTick = -1;
+	m_Info.m_PreviousTick = -1;
+	m_Info.m_Info.m_Speed = 1;
+	
+	m_LastSnapshotDataSize = -1;
+
+	// read the header
+	io_read(m_File, &m_Info.m_Header, sizeof(m_Info.m_Header));
+	if(mem_comp(m_Info.m_Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0)
+	{
+		dbg_msg("demorec/playback", "'%s' is not a demo file", pFilename);
+		io_close(m_File);
+		m_File = 0;
+		return -1;
+	}
+	
+	// scan the file for interessting points
+	ScanFile();
+	
+	// ready for playback
+	return 0;
+}
+
+int CDemoPlayer::NextFrame()
+{
+	DoTick();
+	return IsPlaying();
+}
+
+int CDemoPlayer::Play()
+{
+	// fill in previous and next tick
+	while(m_Info.m_PreviousTick == -1 && IsPlaying())
+		DoTick();
+		
+	// set start info
+	/*m_Info.start_tick = m_Info.previous_tick;
+	m_Info.start_time = time_get();*/
+	m_Info.m_CurrentTime = m_Info.m_PreviousTick*time_freq()/SERVER_TICK_SPEED;
+	m_Info.m_LastUpdate = time_get();
+	return 0;
+}
+
+int CDemoPlayer::SetPos(float Percent)
+{
+	int Keyframe;
+	int WantedTick;
+	if(!m_File)
+		return -1;
+	
+	// -5 because we have to have a current tick and previous tick when we do the playback
+	WantedTick = m_Info.m_Info.m_FirstTick + (int)((m_Info.m_Info.m_LastTick-m_Info.m_Info.m_FirstTick)*Percent) - 5;
+	
+	Keyframe = (int)(m_Info.m_SeekablePoints*Percent);
+
+	if(Keyframe < 0 || Keyframe >= m_Info.m_SeekablePoints)
+		return -1;
+	
+	// get correct key frame
+	if(m_pKeyFrames[Keyframe].m_Tick < WantedTick)
+		while(Keyframe < m_Info.m_SeekablePoints-1 && m_pKeyFrames[Keyframe].m_Tick < WantedTick)
+			Keyframe++;
+
+	while(Keyframe && m_pKeyFrames[Keyframe].m_Tick > WantedTick)
+		Keyframe--;
+	
+	// seek to the correct keyframe
+	io_seek(m_File, m_pKeyFrames[Keyframe].m_Filepos, IOSEEK_START);
+
+	//m_Info.start_tick = -1;
+	m_Info.m_NextTick = -1;
+	m_Info.m_Info.m_CurrentTick = -1;
+	m_Info.m_PreviousTick = -1;
+
+	// playback everything until we hit our tick
+	while(m_Info.m_PreviousTick < WantedTick)
+		DoTick();
+	
+	Play();
+	
+	return 0;
+}
+
+void CDemoPlayer::SetSpeed(float Speed)
+{
+	m_Info.m_Info.m_Speed = Speed;
+}
+
+int CDemoPlayer::Update()
+{
+	int64 Now = time_get();
+	int64 Deltatime = Now-m_Info.m_LastUpdate;
+	m_Info.m_LastUpdate = Now;
+	
+	if(!IsPlaying())
+		return 0;
+	
+	if(m_Info.m_Info.m_Paused)
+	{
+		
+	}
+	else
+	{
+		int64 Freq = time_freq();
+		m_Info.m_CurrentTime += (int64)(Deltatime*(double)m_Info.m_Info.m_Speed);
+		
+		while(1)
+		{
+			int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED;
+
+			// break if we are ready
+			if(CurtickStart > m_Info.m_CurrentTime)
+				break;
+			
+			// do one more tick
+			DoTick();
+			
+			if(m_Info.m_Info.m_Paused)
+				return 0;
+		}
+
+		// update intratick
+		{	
+			int64 CurtickStart = (m_Info.m_Info.m_CurrentTick)*Freq/SERVER_TICK_SPEED;
+			int64 PrevtickStart = (m_Info.m_PreviousTick)*Freq/SERVER_TICK_SPEED;
+			m_Info.m_IntraTick = (m_Info.m_CurrentTime - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
+			m_Info.m_TickTime = (m_Info.m_CurrentTime - PrevtickStart) / (float)Freq;
+		}
+		
+		if(m_Info.m_Info.m_CurrentTick == m_Info.m_PreviousTick ||
+			m_Info.m_Info.m_CurrentTick == m_Info.m_NextTick)
+		{
+			dbg_msg("demorec/playback", "tick error prev=%d cur=%d next=%d",
+				m_Info.m_PreviousTick, m_Info.m_Info.m_CurrentTick, m_Info.m_NextTick);
+		}
+	}
+	
+	return 0;
+}
+
+int CDemoPlayer::Stop()
+{
+	if(!m_File)
+		return -1;
+		
+	dbg_msg("demorec/playback", "Stopped playback");
+	io_close(m_File);
+	m_File = 0;
+	mem_free(m_pKeyFrames);
+	m_pKeyFrames = 0;
+	return 0;
+}
+
+
diff --git a/src/engine/shared/demorec.h b/src/engine/shared/demorec.h
new file mode 100644
index 00000000..0936c30c
--- /dev/null
+++ b/src/engine/shared/demorec.h
@@ -0,0 +1,117 @@
+#ifndef ENGINE_SHARED_DEMOREC_H
+#define ENGINE_SHARED_DEMOREC_H
+
+#include <engine/demo.h>
+#include "snapshot.h"
+
+struct CDemoHeader
+{
+	char m_aMarker[8];
+	char m_aNetversion[64];
+	char m_aMap[64];
+	unsigned char m_aCrc[4];
+	char m_aType[8];
+};
+
+class CDemoRecorder
+{
+	IOHANDLE m_File;
+	int m_LastTickMarker;
+	int m_LastKeyFrame;
+	unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE];
+	class CSnapshotDelta *m_pSnapshotDelta;
+	
+	void WriteTickMarker(int Tick, int Keyframe);
+	void Write(int Type, const void *pData, int Size);
+public:
+	CDemoRecorder(class CSnapshotDelta *pSnapshotDelta);
+	
+	int Start(class IStorage *pStorage, const char *pFilename, const char *pNetversion, const char *pMap, int MapCrc, const char *pType);
+	int Stop();
+
+	void RecordSnapshot(int Tick, const void *pData, int Size);
+	void RecordMessage(const void *pData, int Size);
+
+	bool IsRecording() const { return m_File != 0; }
+};
+
+class CDemoPlayer : public IDemoPlayer
+{
+public:
+	class IListner
+	{
+	public:
+		virtual void OnDemoPlayerSnapshot(void *pData, int Size) = 0;
+		virtual void OnDemoPlayerMessage(void *pData, int Size) = 0;
+	};
+	
+	struct CPlaybackInfo
+	{
+		CDemoHeader m_Header;
+		
+		IDemoPlayer::CInfo m_Info;
+
+		int64 m_LastUpdate;
+		int64 m_CurrentTime;
+		
+		int m_SeekablePoints;
+		
+		int m_NextTick;
+		int m_PreviousTick;
+		
+		float m_IntraTick;
+		float m_TickTime;
+	};
+
+private:
+	IListner *m_pListner;
+
+
+	// Playback
+	struct CKeyFrame
+	{
+		long m_Filepos;
+		int m_Tick;
+	};
+		
+	struct CKeyFrameSearch
+	{
+		CKeyFrame m_Frame;
+		CKeyFrameSearch *m_pNext;
+	};	
+
+	IOHANDLE m_File;
+	CKeyFrame *m_pKeyFrames;
+
+	CPlaybackInfo m_Info;
+	unsigned char m_aLastSnapshotData[CSnapshot::MAX_SIZE];
+	int m_LastSnapshotDataSize;
+	class CSnapshotDelta *m_pSnapshotDelta;
+
+	int ReadChunkHeader(int *pType, int *pSize, int *pTick);
+	void DoTick();
+	void ScanFile();
+	int NextFrame();
+
+public:
+	
+	CDemoPlayer(class CSnapshotDelta *m_pSnapshotDelta);
+	
+	void SetListner(IListner *pListner);
+		
+	int Load(class IStorage *pStorage, const char *pFilename);
+	int Play();
+	void Pause();
+	void Unpause();
+	int Stop();	
+	void SetSpeed(float Speed);
+	int SetPos(float Precent);
+	const CInfo *BaseInfo() const { return &m_Info.m_Info; }
+	
+	int Update();
+	
+	const CPlaybackInfo *Info() const { return &m_Info; }
+	int IsPlaying() const { return m_File != 0; }
+};
+
+#endif
diff --git a/src/engine/shared/engine.cpp b/src/engine/shared/engine.cpp
new file mode 100644
index 00000000..5cd50cf0
--- /dev/null
+++ b/src/engine/shared/engine.cpp
@@ -0,0 +1,76 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+
+#include <base/system.h>
+
+#include <engine/shared/config.h>
+#include <engine/shared/engine.h>
+#include <engine/shared/network.h>
+#include <engine/console.h>
+#include "linereader.h"
+
+// compiled-in data-dir path
+#define DATA_DIR "data"
+
+//static int engine_find_datadir(char *argv0);
+/*
+static void con_dbg_dumpmem(IConsole::IResult *result, void *user_data)
+{
+	mem_debug_dump();
+}
+
+static void con_dbg_lognetwork(IConsole::IResult *result, void *user_data)
+{
+	CNetBase::OpenLog("network_sent.dat", "network_recv.dat");
+}*/
+
+/*
+static char application_save_path[512] = {0};
+static char datadir[512] = {0};
+
+const char *engine_savepath(const char *filename, char *buffer, int max)
+{
+	str_format(buffer, max, "%s/%s", application_save_path, filename);
+	return buffer;
+}*/
+
+void CEngine::Init(const char *pAppname)
+{
+	dbg_logger_stdout();
+	dbg_logger_debugger();
+	
+	//
+	dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING);
+#ifdef CONF_ARCH_ENDIAN_LITTLE
+	dbg_msg("engine", "arch is little endian");
+#elif defined(CONF_ARCH_ENDIAN_BIG)
+	dbg_msg("engine", "arch is big endian");
+#else
+	dbg_msg("engine", "unknown endian");
+#endif
+
+	// init the network
+	net_init();
+	CNetBase::Init();
+	
+	m_HostLookupPool.Init(1);
+
+	//MACRO_REGISTER_COMMAND("dbg_dumpmem", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_dumpmem, 0x0, "Dump the memory");
+	//MACRO_REGISTER_COMMAND("dbg_lognetwork", "", CFGFLAG_SERVER|CFGFLAG_CLIENT, con_dbg_lognetwork, 0x0, "Log the network");
+	
+	// reset the config
+	//config_reset();
+}
+
+
+static int HostLookupThread(void *pUser)
+{
+	CHostLookup *pLookup = (CHostLookup *)pUser;
+	net_host_lookup(pLookup->m_aHostname, &pLookup->m_Addr, NETTYPE_IPV4);
+	return 0;
+}
+
+void CEngine::HostLookup(CHostLookup *pLookup, const char *pHostname)
+{
+	str_copy(pLookup->m_aHostname, pHostname, sizeof(pLookup->m_aHostname));
+	m_HostLookupPool.Add(&pLookup->m_Job, HostLookupThread, pLookup);
+}
diff --git a/src/engine/shared/engine.h b/src/engine/shared/engine.h
new file mode 100644
index 00000000..ad266ae4
--- /dev/null
+++ b/src/engine/shared/engine.h
@@ -0,0 +1,23 @@
+#ifndef ENGINE_SHARED_E_ENGINE_H
+#define ENGINE_SHARED_E_ENGINE_H
+
+#include "jobs.h"
+
+class CHostLookup
+{
+public:
+	CJob m_Job;
+	char m_aHostname[128];
+	NETADDR m_Addr;
+};
+
+class CEngine
+{
+	class CJobPool m_HostLookupPool;
+
+public:
+	void Init(const char *pAppname);
+	void HostLookup(CHostLookup *pLookup, const char *pHostname);
+};
+
+#endif
diff --git a/src/engine/shared/huffman.cpp b/src/engine/shared/huffman.cpp
new file mode 100644
index 00000000..8b0c1cd0
--- /dev/null
+++ b/src/engine/shared/huffman.cpp
@@ -0,0 +1,276 @@
+#include <base/system.h>
+#include "huffman.h"
+
+struct CHuffmanConstructNode
+{
+	unsigned short m_NodeId;
+ 	int m_Frequency;
+};
+
+void CHuffman::Setbits_r(CNode *pNode, int Bits, int Depth)
+{
+	if(pNode->m_aLeafs[1] != 0xffff)
+		Setbits_r(&m_aNodes[pNode->m_aLeafs[1]], Bits|(1<<Depth), Depth+1);
+	if(pNode->m_aLeafs[0] != 0xffff)
+		Setbits_r(&m_aNodes[pNode->m_aLeafs[0]], Bits, Depth+1);
+		
+	if(pNode->m_NumBits)
+	{
+		pNode->m_Bits = Bits;
+		pNode->m_NumBits = Depth;
+	}
+}
+
+// TODO: this should be something faster, but it's enough for now
+static void BubbleSort(CHuffmanConstructNode **ppList, int Size)
+{
+	int Changed = 1;
+	CHuffmanConstructNode *pTemp;
+	
+	while(Changed)
+	{
+		Changed = 0;
+		for(int i = 0; i < Size-1; i++)
+		{
+			if(ppList[i]->m_Frequency < ppList[i+1]->m_Frequency)
+			{
+				pTemp = ppList[i];
+				ppList[i] = ppList[i+1];
+				ppList[i+1] = pTemp;
+				Changed = 1;
+			}
+		}
+	}
+}
+
+void CHuffman::ConstructTree(const unsigned *pFrequencies)
+{
+	CHuffmanConstructNode aNodesLeftStorage[HUFFMAN_MAX_SYMBOLS];
+	CHuffmanConstructNode *apNodesLeft[HUFFMAN_MAX_SYMBOLS];
+	int NumNodesLeft = HUFFMAN_MAX_SYMBOLS;
+
+	// add the symbols
+	for(int i = 0; i < HUFFMAN_MAX_SYMBOLS; i++)
+	{
+		m_aNodes[i].m_NumBits = -1;
+		m_aNodes[i].m_Symbol = i;
+		m_aNodes[i].m_aLeafs[0] = -1;
+		m_aNodes[i].m_aLeafs[1] = -1;
+
+		if(i == HUFFMAN_EOF_SYMBOL)
+			aNodesLeftStorage[i].m_Frequency = 1;
+		else
+			aNodesLeftStorage[i].m_Frequency = pFrequencies[i];
+		aNodesLeftStorage[i].m_NodeId = i;
+		apNodesLeft[i] = &aNodesLeftStorage[i];
+
+	}
+	
+	m_NumNodes = HUFFMAN_MAX_SYMBOLS;
+	
+	// construct the table
+	while(NumNodesLeft > 1)
+	{
+		// we can't rely on stdlib's qsort for this, it can generate different results on different implementations
+		BubbleSort(apNodesLeft, NumNodesLeft);
+		
+		m_aNodes[m_NumNodes].m_NumBits = 0;
+		m_aNodes[m_NumNodes].m_aLeafs[0] = apNodesLeft[NumNodesLeft-1]->m_NodeId;
+		m_aNodes[m_NumNodes].m_aLeafs[1] = apNodesLeft[NumNodesLeft-2]->m_NodeId;
+		apNodesLeft[NumNodesLeft-2]->m_NodeId = m_NumNodes;
+		apNodesLeft[NumNodesLeft-2]->m_Frequency = apNodesLeft[NumNodesLeft-1]->m_Frequency + apNodesLeft[NumNodesLeft-2]->m_Frequency;
+
+		m_NumNodes++;
+		NumNodesLeft--;
+	}
+
+	// set start node
+	m_pStartNode = &m_aNodes[m_NumNodes-1];
+	
+	// build symbol bits
+	Setbits_r(m_pStartNode, 0, 0);
+}
+
+void CHuffman::Init(const unsigned *pFrequencies)
+{
+	int i;
+
+	// make sure to cleanout every thing
+	mem_zero(this, sizeof(*this));
+
+	// construct the tree
+	ConstructTree(pFrequencies);
+
+	// build decode LUT
+	for(i = 0; i < HUFFMAN_LUTSIZE; i++)
+	{
+		unsigned Bits = i;
+		int k;
+		CNode *pNode = m_pStartNode;
+		for(k = 0; k < HUFFMAN_LUTBITS; k++)
+		{
+			pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]];
+			Bits >>= 1;
+
+			if(!pNode)
+				break;
+
+			if(pNode->m_NumBits)
+			{
+				m_apDecodeLut[i] = pNode;
+				break;
+			}
+		}
+
+		if(k == HUFFMAN_LUTBITS)
+			m_apDecodeLut[i] = pNode;
+	}
+
+}
+
+//***************************************************************
+int CHuffman::Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize)
+{
+	// this macro loads a symbol for a byte into bits and bitcount
+#define HUFFMAN_MACRO_LOADSYMBOL(Sym) \
+	Bits |= m_aNodes[Sym].m_Bits << Bitcount; \
+	Bitcount += m_aNodes[Sym].m_NumBits;
+
+	// this macro writes the symbol stored in bits and bitcount to the dst pointer
+#define HUFFMAN_MACRO_WRITE() \
+	while(Bitcount >= 8) \
+	{ \
+		*pDst++ = (unsigned char)(Bits&0xff); \
+		if(pDst == pDstEnd) \
+			return -1; \
+		Bits >>= 8; \
+		Bitcount -= 8; \
+	}
+
+	// setup buffer pointers
+	const unsigned char *pSrc = (const unsigned char *)pInput;
+	const unsigned char *pSrcEnd = pSrc + InputSize;
+	unsigned char *pDst = (unsigned char *)pOutput;
+	unsigned char *pDstEnd = pDst + OutputSize;
+
+	// symbol variables
+	unsigned Bits = 0;
+	unsigned Bitcount = 0;
+
+	// make sure that we have data that we want to compress
+	if(InputSize)
+	{
+		// {A} load the first symbol
+		int Symbol = *pSrc++;
+
+		while(pSrc != pSrcEnd)
+		{
+			// {B} load the symbol
+			HUFFMAN_MACRO_LOADSYMBOL(Symbol)
+
+			// {C} fetch next symbol, this is done here because it will reduce dependency in the code
+			Symbol = *pSrc++;
+
+			// {B} write the symbol loaded at
+			HUFFMAN_MACRO_WRITE()
+		}
+
+		// write the last symbol loaded from {C} or {A} in the case of only 1 byte input buffer
+		HUFFMAN_MACRO_LOADSYMBOL(Symbol)
+		HUFFMAN_MACRO_WRITE()
+	}
+
+	// write EOF symbol
+	HUFFMAN_MACRO_LOADSYMBOL(HUFFMAN_EOF_SYMBOL)
+	HUFFMAN_MACRO_WRITE()
+
+	// write out the last bits
+	*pDst++ = Bits;
+
+	// return the size of the output
+	return (int)(pDst - (const unsigned char *)pOutput);
+
+	// remove macros
+#undef HUFFMAN_MACRO_LOADSYMBOL
+#undef HUFFMAN_MACRO_WRITE
+}
+
+//***************************************************************
+int CHuffman::Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize)
+{
+	// setup buffer pointers
+	unsigned char *pDst = (unsigned char *)pOutput;
+	unsigned char *pSrc = (unsigned char *)pInput;
+	unsigned char *pDstEnd = pDst + OutputSize;
+	unsigned char *pSrcEnd = pSrc + InputSize;
+
+	unsigned Bits = 0;
+	unsigned Bitcount = 0;
+
+	CNode *pEof = &m_aNodes[HUFFMAN_EOF_SYMBOL];
+	CNode *pNode = 0;
+
+	while(1)
+	{
+		// {A} try to load a node now, this will reduce dependency at location {D}
+		pNode = 0;
+		if(Bitcount >= HUFFMAN_LUTBITS)
+			pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK];
+
+		// {B} fill with new bits
+		while(Bitcount < 24 && pSrc != pSrcEnd)
+		{
+			Bits |= (*pSrc++) << Bitcount;
+			Bitcount += 8;
+		}
+
+		// {C} load symbol now if we didn't that earlier at location {A}
+		if(!pNode)
+			pNode = m_apDecodeLut[Bits&HUFFMAN_LUTMASK];
+
+		// {D} check if we hit a symbol already
+		if(pNode->m_NumBits)
+		{
+			// remove the bits for that symbol
+			Bits >>= pNode->m_NumBits;
+			Bitcount -= pNode->m_NumBits;
+		}
+		else
+		{
+			// remove the bits that the lut checked up for us
+			Bits >>= HUFFMAN_LUTBITS;
+			Bitcount -= HUFFMAN_LUTBITS;
+
+			// walk the tree bit by bit
+			while(1)
+			{
+				// traverse tree
+				pNode = &m_aNodes[pNode->m_aLeafs[Bits&1]];
+
+				// remove bit
+				Bitcount--;
+				Bits >>= 1;
+
+				// check if we hit a symbol
+				if(pNode->m_NumBits)
+					break;
+
+				// no more bits, decoding error
+				if(Bitcount == 0)
+					return -1;
+			}
+		}
+
+		// check for eof
+		if(pNode == pEof)
+			break;
+
+		// output character
+		if(pDst == pDstEnd)
+			return -1;
+		*pDst++ = pNode->m_Symbol;
+	}
+
+	// return the size of the decompressed buffer
+	return (int)(pDst - (const unsigned char *)pOutput);
+}
diff --git a/src/engine/shared/huffman.h b/src/engine/shared/huffman.h
new file mode 100644
index 00000000..5aa56c8f
--- /dev/null
+++ b/src/engine/shared/huffman.h
@@ -0,0 +1,89 @@
+#ifndef ENGINE_SHARED_HUFFMAN_H
+#define ENGINE_SHARED_HUFFMAN_H
+
+
+
+class CHuffman
+{
+	enum
+	{
+		HUFFMAN_EOF_SYMBOL = 256,
+
+		HUFFMAN_MAX_SYMBOLS=HUFFMAN_EOF_SYMBOL+1,
+		HUFFMAN_MAX_NODES=HUFFMAN_MAX_SYMBOLS*2-1,
+		
+		HUFFMAN_LUTBITS = 10,
+		HUFFMAN_LUTSIZE = (1<<HUFFMAN_LUTBITS),
+		HUFFMAN_LUTMASK = (HUFFMAN_LUTSIZE-1)
+	};
+
+	struct CNode
+	{
+		// symbol
+		unsigned m_Bits;
+		unsigned m_NumBits;
+
+		// don't use pointers for this. shorts are smaller so we can fit more data into the cache
+		unsigned short m_aLeafs[2];
+
+		// what the symbol represents
+		unsigned char m_Symbol;
+	};
+
+	CNode m_aNodes[HUFFMAN_MAX_NODES];
+	CNode *m_apDecodeLut[HUFFMAN_LUTSIZE];
+	CNode *m_pStartNode;
+	int m_NumNodes;
+	
+	void Setbits_r(CNode *pNode, int Bits, int Depth);
+	void ConstructTree(const unsigned *pFrequencies);
+	
+public:
+	/*
+		Function: huffman_init
+			Inits the compressor/decompressor.
+
+		Parameters:
+			huff - Pointer to the state to init
+			frequencies - A pointer to an array of 256 entries of the frequencies of the bytes
+
+		Remarks:
+			- Does no allocation what so ever.
+			- You don't have to call any cleanup functions when you are done with it
+	*/
+	void Init(const unsigned *pFrequencies);
+
+	/*
+		Function: huffman_compress
+			Compresses a buffer and outputs a compressed buffer.
+
+		Parameters:
+			huff - Pointer to the huffman state
+			input - Buffer to compress
+			input_size - Size of the buffer to compress
+			output - Buffer to put the compressed data into
+			output_size - Size of the output buffer
+
+		Returns:
+			Returns the size of the compressed data. Negative value on failure.
+	*/
+	int Compress(const void *pInput, int InputSize, void *pOutput, int OutputSize);
+
+	/*
+		Function: huffman_decompress
+			Decompresses a buffer
+
+		Parameters:
+			huff - Pointer to the huffman state
+			input - Buffer to decompress
+			input_size - Size of the buffer to decompress
+			output - Buffer to put the uncompressed data into
+			output_size - Size of the output buffer
+
+		Returns:
+			Returns the size of the uncompressed data. Negative value on failure.
+	*/
+	int Decompress(const void *pInput, int InputSize, void *pOutput, int OutputSize);
+	
+};
+#endif // __HUFFMAN_HEADER__
diff --git a/src/engine/shared/jobs.cpp b/src/engine/shared/jobs.cpp
new file mode 100644
index 00000000..83d7290b
--- /dev/null
+++ b/src/engine/shared/jobs.cpp
@@ -0,0 +1,74 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include "jobs.h"
+
+CJobPool::CJobPool()
+{
+	// empty the pool
+	m_Lock = lock_create();
+	m_pFirstJob = 0;
+	m_pLastJob = 0;
+}
+
+void CJobPool::WorkerThread(void *pUser)
+{
+	CJobPool *pPool = (CJobPool *)pUser;
+	
+	while(1)
+	{
+		CJob *pJob = 0;
+		
+		// fetch job from queue
+		lock_wait(pPool->m_Lock);
+		if(pPool->m_pFirstJob)
+		{
+			pJob = pPool->m_pFirstJob;
+			pPool->m_pFirstJob = pPool->m_pFirstJob->m_pNext;
+			if(pPool->m_pFirstJob)
+				pPool->m_pFirstJob->m_pPrev = 0;
+			else
+				pPool->m_pLastJob = 0;
+		}
+		lock_release(pPool->m_Lock);
+		
+		// do the job if we have one
+		if(pJob)
+		{
+			pJob->m_Status = CJob::STATE_RUNNING;
+			pJob->m_Result = pJob->m_pfnFunc(pJob->m_pFuncData);
+			pJob->m_Status = CJob::STATE_DONE;
+		}
+		else
+			thread_sleep(10);
+	}
+	
+}
+
+int CJobPool::Init(int NumThreads)
+{
+	// start threads
+	for(int i = 0; i < NumThreads; i++)
+		thread_create(WorkerThread, this);
+	return 0;
+}
+
+int CJobPool::Add(CJob *pJob, JOBFUNC pfnFunc, void *pData)
+{
+	mem_zero(pJob, sizeof(CJob));
+	pJob->m_pfnFunc = pfnFunc;
+	pJob->m_pFuncData = pData;
+	
+	lock_wait(m_Lock);
+	
+	// add job to queue
+	pJob->m_pPrev = m_pLastJob;
+	if(m_pLastJob)
+		m_pLastJob->m_pNext = pJob;
+	m_pLastJob = pJob;
+	if(!m_pFirstJob)
+		m_pFirstJob = pJob;
+	
+	lock_release(m_Lock);
+	return 0;
+}
+
diff --git a/src/engine/shared/jobs.h b/src/engine/shared/jobs.h
new file mode 100644
index 00000000..d04815b0
--- /dev/null
+++ b/src/engine/shared/jobs.h
@@ -0,0 +1,51 @@
+#ifndef ENGINE_SHARED_JOBS_H
+#define ENGINE_SHARED_JOBS_H
+typedef int (*JOBFUNC)(void *pData);
+
+class CJobPool;
+
+class CJob
+{
+	friend class CJobPool;
+	
+	CJobPool *m_pPool;
+	CJob *m_pPrev;
+	CJob *m_pNext;
+	
+	volatile int m_Status;
+	volatile int m_Result;
+	
+	JOBFUNC m_pfnFunc;
+	void *m_pFuncData;
+public:
+	CJob()
+	{
+		m_Status = STATE_DONE;
+		m_pFuncData = 0;
+	}
+	
+	enum
+	{
+		STATE_PENDING=0,
+		STATE_RUNNING,
+		STATE_DONE
+	};
+	
+	int Status() const { return m_Status; }
+};
+
+class CJobPool
+{
+	LOCK m_Lock;
+	CJob *m_pFirstJob;
+	CJob *m_pLastJob;
+	
+	static void WorkerThread(void *pUser);
+	
+public:
+	CJobPool();
+	
+	int Init(int NumThreads);
+	int Add(CJob *pJob, JOBFUNC pfnFunc, void *pData);
+};
+#endif
diff --git a/src/engine/shared/kernel.cpp b/src/engine/shared/kernel.cpp
new file mode 100644
index 00000000..9f6850ba
--- /dev/null
+++ b/src/engine/shared/kernel.cpp
@@ -0,0 +1,93 @@
+#include <base/system.h>
+#include <engine/kernel.h>
+
+class CKernel : public IKernel
+{
+	enum
+	{
+		MAX_INTERFACES=32,
+	};
+	
+	class CInterfaceInfo
+	{
+	public:
+		CInterfaceInfo()
+		{
+			m_aName[0] = 0;
+			m_pInterface = 0x0;
+		}
+		
+		char m_aName[64];
+		IInterface *m_pInterface;
+	};
+
+	CInterfaceInfo m_aInterfaces[MAX_INTERFACES];
+	int m_NumInterfaces;
+	
+	CInterfaceInfo *FindInterfaceInfo(const char *pName)
+	{
+		for(int i = 0; i < m_NumInterfaces; i++)
+		{
+			if(str_comp(pName, m_aInterfaces[i].m_aName) == 0)
+				return &m_aInterfaces[i];
+		}
+		return 0x0;
+	}
+	
+public:
+
+	CKernel()
+	{
+		m_NumInterfaces = 0;
+	}
+
+
+	virtual bool RegisterInterfaceImpl(const char *pName, IInterface *pInterface)
+	{
+		// TODO: More error checks here
+		if(m_NumInterfaces == MAX_INTERFACES)
+		{
+			dbg_msg("kernel", "ERROR: couldn't register interface '%s'. maximum of interfaces reached", pName);
+			return false;
+		}
+			
+		if(FindInterfaceInfo(pName) != 0)
+		{
+			dbg_msg("kernel", "ERROR: couldn't register interface '%s'. interface already exists");
+			return false;
+		}
+		
+		pInterface->m_pKernel = this;
+		m_aInterfaces[m_NumInterfaces].m_pInterface = pInterface;
+		str_copy(m_aInterfaces[m_NumInterfaces].m_aName, pName, sizeof(m_aInterfaces[m_NumInterfaces].m_aName));
+		m_NumInterfaces++;
+		
+		return true;
+	}
+
+	virtual bool ReregisterInterfaceImpl(const char *pName, IInterface *pInterface)
+	{
+		if(FindInterfaceInfo(pName) == 0)
+		{
+			dbg_msg("kernel", "ERROR: couldn't reregister interface '%s'. interface doesn't exist");
+			return false;
+		}
+		
+		pInterface->m_pKernel = this;
+		
+		return true;
+	}
+	
+	virtual IInterface *RequestInterfaceImpl(const char *pName)
+	{
+		CInterfaceInfo *pInfo = FindInterfaceInfo(pName);
+		if(!pInfo)
+		{
+			dbg_msg("kernel", "failed to find interface with the name '%s'", pName);
+			return 0;
+		}
+		return pInfo->m_pInterface;
+	}
+};
+
+IKernel *IKernel::Create() { return new CKernel; }
diff --git a/src/engine/shared/linereader.cpp b/src/engine/shared/linereader.cpp
new file mode 100644
index 00000000..b3de233b
--- /dev/null
+++ b/src/engine/shared/linereader.cpp
@@ -0,0 +1,62 @@
+#include "linereader.h"
+
+void CLineReader::Init(IOHANDLE io)
+{
+	m_BufferMaxSize = 4*1024;
+	m_BufferSize = 0;
+	m_BufferPos = 0;
+	m_IO = io;
+}
+
+char *CLineReader::Get()
+{
+	unsigned LineStart = m_BufferPos;
+
+	while(1)
+	{
+		if(m_BufferPos >= m_BufferSize)
+		{
+			// fetch more
+
+			// move the remaining part to the front
+			unsigned Read;
+			unsigned Left = m_BufferSize - LineStart;
+
+			if(LineStart > m_BufferSize)
+				Left = 0;
+			if(Left)
+				mem_move(m_aBuffer, &m_aBuffer[LineStart], Left);
+			m_BufferPos = Left;
+
+			// fill the buffer
+			Read = io_read(m_IO, &m_aBuffer[m_BufferPos], m_BufferMaxSize-m_BufferPos);
+			m_BufferSize = Left + Read;
+			LineStart = 0;
+
+			if(!Read)
+			{
+				if(Left)
+				{
+					m_aBuffer[Left] = 0; // return the last line
+					m_BufferPos = Left;
+					m_BufferSize = Left;
+					return m_aBuffer;
+				}
+				else
+					return 0x0; // we are done!
+			}
+		}
+		else
+		{
+			if(m_aBuffer[m_BufferPos] == '\n' || m_aBuffer[m_BufferPos] == '\r')
+			{
+				// line found
+				m_aBuffer[m_BufferPos] = 0;
+				m_BufferPos++;
+				return &m_aBuffer[LineStart];
+			}
+			else
+				m_BufferPos++;
+		}
+	}
+}
diff --git a/src/engine/shared/linereader.h b/src/engine/shared/linereader.h
new file mode 100644
index 00000000..f28d42f6
--- /dev/null
+++ b/src/engine/shared/linereader.h
@@ -0,0 +1,17 @@
+#ifndef ENGINE_SHARED_LINEREADER_H
+#define ENGINE_SHARED_LINEREADER_H
+#include <base/system.h>
+
+// buffered stream for reading lines, should perhaps be something smaller
+class CLineReader
+{
+	char m_aBuffer[4*1024];
+	unsigned m_BufferPos;
+	unsigned m_BufferSize;
+	unsigned m_BufferMaxSize;
+	IOHANDLE m_IO;
+public:
+	void Init(IOHANDLE IoHandle);
+	char *Get();
+};
+#endif
diff --git a/src/engine/shared/map.cpp b/src/engine/shared/map.cpp
new file mode 100644
index 00000000..505d18e9
--- /dev/null
+++ b/src/engine/shared/map.cpp
@@ -0,0 +1,45 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include <engine/map.h>
+#include <engine/storage.h>
+#include "datafile.h"
+
+class CMap : public IEngineMap
+{
+	CDataFileReader m_DataFile;
+public:
+	CMap() {}
+	
+	virtual void *GetData(int Index) { return m_DataFile.GetData(Index); }
+	virtual void *GetDataSwapped(int Index) { return m_DataFile.GetDataSwapped(Index); }
+	virtual void UnloadData(int Index) { m_DataFile.UnloadData(Index); }
+	virtual void *GetItem(int Index, int *pType, int *pId) { return m_DataFile.GetItem(Index, pType, pId); }
+	virtual void GetType(int Type, int *pStart, int *pNum) { m_DataFile.GetType(Type, pStart, pNum); }
+	virtual void *FindItem(int Type, int Id) { return m_DataFile.FindItem(Type, Id); }
+	virtual int NumItems() { return m_DataFile.NumItems(); }
+	
+	virtual void Unload()
+	{
+		m_DataFile.Close();
+	}
+
+	virtual bool Load(const char *pMapName)
+	{
+		IStorage *pStorage = Kernel()->RequestInterface<IStorage>();
+		if(!pStorage)
+			return false;
+		return m_DataFile.Open(pStorage, pMapName);
+	}
+	
+	virtual bool IsLoaded()
+	{
+		return m_DataFile.IsOpen();
+	}
+	
+	virtual unsigned Crc()
+	{
+		return m_DataFile.Crc();
+	}
+};
+
+extern IEngineMap *CreateEngineMap() { return new CMap; }
diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp
new file mode 100644
index 00000000..beade5bf
--- /dev/null
+++ b/src/engine/shared/masterserver.cpp
@@ -0,0 +1,187 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <stdio.h>
+
+#include <base/system.h>
+#include <engine/masterserver.h>
+#include <engine/storage.h>
+#include "engine.h"
+#include "linereader.h"
+
+class CMasterServer : public IEngineMasterServer
+{
+public:
+	// master server functions
+	struct CMasterInfo
+	{
+		char m_aHostname[128];
+		NETADDR m_Addr;
+		
+		CHostLookup m_Lookup;
+	} ;
+
+	CMasterInfo m_aMasterServers[MAX_MASTERSERVERS];
+	int m_NeedsUpdate;
+	CEngine *m_pEngine;
+	
+	CMasterServer()
+	{
+		SetDefault();
+		m_NeedsUpdate = -1;
+		m_pEngine = 0;
+	}
+
+	virtual int RefreshAddresses()
+	{
+		int i;
+		
+		if(m_NeedsUpdate != -1)
+			return 0;
+		
+		dbg_msg("engine/mastersrv", "refreshing master server addresses");
+
+		// add lookup jobs
+		for(i = 0; i < MAX_MASTERSERVERS; i++)	
+			m_pEngine->HostLookup(&m_aMasterServers[i].m_Lookup, m_aMasterServers[i].m_aHostname);
+		
+		m_NeedsUpdate = 1;
+		return 0;
+	}
+
+	virtual void Update()
+	{
+		// check if we need to update
+		if(m_NeedsUpdate != 1)
+			return;
+		m_NeedsUpdate = 0;
+		
+		for(int i = 0; i < MAX_MASTERSERVERS; i++)
+		{
+			if(m_aMasterServers[i].m_Lookup.m_Job.Status() != CJob::STATE_DONE)
+				m_NeedsUpdate = 1;
+			else
+			{
+				m_aMasterServers[i].m_Addr = m_aMasterServers[i].m_Lookup.m_Addr;
+				m_aMasterServers[i].m_Addr.port = 8300;
+			}
+		}
+		
+		if(!m_NeedsUpdate)
+		{
+			dbg_msg("engine/mastersrv", "saving addresses");
+			Save();
+		}
+	}
+
+	virtual int IsRefreshing()
+	{
+		return m_NeedsUpdate;
+	}
+
+	virtual NETADDR GetAddr(int Index) 
+	{
+		return m_aMasterServers[Index].m_Addr;
+	}
+
+	virtual const char *GetName(int Index) 
+	{
+		return m_aMasterServers[Index].m_aHostname;
+	}
+
+	virtual void DumpServers()
+	{
+		for(int i = 0; i < MAX_MASTERSERVERS; i++)
+		{
+			dbg_msg("mastersrv", "#%d = %d.%d.%d.%d", i,
+				m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1],
+				m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]);
+		}
+	}
+
+	virtual void Init(class CEngine *pEngine)
+	{
+		m_pEngine = pEngine;
+	}
+
+	virtual void SetDefault()
+	{
+		mem_zero(m_aMasterServers, sizeof(m_aMasterServers));
+		for(int i = 0; i < MAX_MASTERSERVERS; i++)
+			str_format(m_aMasterServers[i].m_aHostname, sizeof(m_aMasterServers[i].m_aHostname), "master%d.teeworlds.com", i+1);
+	}
+
+	virtual int Load()
+	{
+		CLineReader LineReader;
+		IOHANDLE File;
+		int Count = 0;
+		IStorage *pStorage = Kernel()->RequestInterface<IStorage>();
+		if(!pStorage)
+			return -1;
+		
+		// try to open file
+		File = pStorage->OpenFile("masters.cfg", IOFLAG_READ);
+		if(!File)
+			return -1;
+		
+		LineReader.Init(File);
+		while(1)
+		{
+			CMasterInfo Info = {{0}};
+			int aIp[4];
+			const char *pLine = LineReader.Get();
+			if(!pLine)
+				break;
+
+			// parse line	
+			if(sscanf(pLine, "%s %d.%d.%d.%d", Info.m_aHostname, &aIp[0], &aIp[1], &aIp[2], &aIp[3]) == 5)
+			{
+				Info.m_Addr.ip[0] = (unsigned char)aIp[0];
+				Info.m_Addr.ip[1] = (unsigned char)aIp[1];
+				Info.m_Addr.ip[2] = (unsigned char)aIp[2];
+				Info.m_Addr.ip[3] = (unsigned char)aIp[3];
+				Info.m_Addr.port = 8300;
+				if(Count != MAX_MASTERSERVERS)
+				{
+					m_aMasterServers[Count] = Info;
+					Count++;
+				}
+				//else
+				//	dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", pLine, MAX_MASTERSERVERS);
+			}
+			//else
+			//	dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", pLine);
+		}
+		
+		io_close(File);
+		return 0;
+	}
+
+	virtual int Save()
+	{
+		IOHANDLE File;
+
+		IStorage *pStorage = Kernel()->RequestInterface<IStorage>();
+		if(!pStorage)
+			return -1;
+			
+		// try to open file
+		File = pStorage->OpenFile("masters.cfg", IOFLAG_WRITE);
+		if(!File)
+			return -1;
+
+		for(int i = 0; i < MAX_MASTERSERVERS; i++)
+		{
+			char aBuf[1024];
+			str_format(aBuf, sizeof(aBuf), "%s %d.%d.%d.%d\n", m_aMasterServers[i].m_aHostname,
+				m_aMasterServers[i].m_Addr.ip[0], m_aMasterServers[i].m_Addr.ip[1],
+				m_aMasterServers[i].m_Addr.ip[2], m_aMasterServers[i].m_Addr.ip[3]);
+				
+			io_write(File, aBuf, str_length(aBuf));
+		}
+		
+		io_close(File);
+		return 0;
+	}
+};
+
+IEngineMasterServer *CreateEngineMasterServer() { return new CMasterServer; }
diff --git a/src/engine/shared/memheap.cpp b/src/engine/shared/memheap.cpp
new file mode 100644
index 00000000..6661962b
--- /dev/null
+++ b/src/engine/shared/memheap.cpp
@@ -0,0 +1,96 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include "memheap.h"
+
+static const int CHUNK_SIZE = 1024*64;
+
+// allocates a new chunk to be used
+void CHeap::NewChunk()
+{
+	CChunk *pChunk;
+	char *pMem;
+	
+	// allocate memory
+	pMem = (char*)mem_alloc(sizeof(CChunk)+CHUNK_SIZE, 1);
+	if(!pMem)
+		return;
+
+	// the chunk structure is located in the begining of the chunk
+	// init it and return the chunk
+	pChunk = (CChunk*)pMem;
+	pChunk->m_pMemory = (char*)(pChunk+1);
+	pChunk->m_pCurrent = pChunk->m_pMemory;
+	pChunk->m_pEnd = pChunk->m_pMemory + CHUNK_SIZE;
+	pChunk->m_pNext = (CChunk *)0x0;
+
+	pChunk->m_pNext = m_pCurrent;
+	m_pCurrent = pChunk;	
+}
+
+//****************
+void *CHeap::AllocateFromChunk(unsigned int Size)
+{
+	char *pMem;
+	
+	// check if we need can fit the allocation
+	if(m_pCurrent->m_pCurrent + Size > m_pCurrent->m_pEnd)
+		return (void*)0x0;
+
+	// get memory and move the pointer forward
+	pMem = m_pCurrent->m_pCurrent;
+	m_pCurrent->m_pCurrent += Size;
+	return pMem;
+}
+
+// creates a heap
+CHeap::CHeap()
+{
+	m_pCurrent = 0x0;
+	Reset();
+}
+
+CHeap::~CHeap()
+{
+	Clear();
+}
+
+void CHeap::Reset()
+{
+	Clear();
+	NewChunk();
+}
+
+// destroys the heap
+void CHeap::Clear()
+{
+	CChunk *pChunk = m_pCurrent;
+	CChunk *pNext;
+	
+	while(pChunk)
+	{
+		pNext = pChunk->m_pNext;
+		mem_free(pChunk);
+		pChunk = pNext;
+	}
+	
+	m_pCurrent = 0x0;
+}
+
+//
+void *CHeap::Allocate(unsigned Size)
+{
+	char *pMem;
+
+	// try to allocate from current chunk
+	pMem = (char *)AllocateFromChunk(Size);
+	if(!pMem)
+	{
+		// allocate new chunk and add it to the heap
+		NewChunk();
+		
+		// try to allocate again
+		pMem = (char *)AllocateFromChunk(Size);
+	}
+	
+	return pMem;
+}
diff --git a/src/engine/shared/memheap.h b/src/engine/shared/memheap.h
new file mode 100644
index 00000000..706395f2
--- /dev/null
+++ b/src/engine/shared/memheap.h
@@ -0,0 +1,32 @@
+#ifndef ENGINE_SHARED_MEMHEAP_H
+#define ENGINE_SHARED_MEMHEAP_H
+class CHeap
+{
+	struct CChunk
+	{
+		char *m_pMemory;
+		char *m_pCurrent;
+		char *m_pEnd;
+		CChunk *m_pNext;
+	};
+	
+	enum
+	{
+		// how large each chunk should be
+		CHUNK_SIZE = 1025*64,
+	};
+	
+	CChunk *m_pCurrent;
+	
+	
+	void Clear();
+	void NewChunk();
+	void *AllocateFromChunk(unsigned int Size);
+	
+public:
+	CHeap();
+	~CHeap();
+	void Reset();
+	void *Allocate(unsigned Size);
+};
+#endif
diff --git a/src/engine/shared/message.h b/src/engine/shared/message.h
new file mode 100644
index 00000000..4e67a8e1
--- /dev/null
+++ b/src/engine/shared/message.h
@@ -0,0 +1,9 @@
+#ifndef ENGINE_SHARED_MESSAGE_H
+#define ENGINE_SHARED_MESSAGE_H
+class CMessage
+{
+public:
+	virtual bool Pack(void *pData, unsigned MaxDataSize);
+	virtual bool Unpack(const void *pData, unsigned DataSize);
+};
+#endif
diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp
new file mode 100644
index 00000000..0305ffff
--- /dev/null
+++ b/src/engine/shared/network.cpp
@@ -0,0 +1,347 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+
+
+#include "config.h"
+#include "engine.h"
+#include "network.h"
+#include "huffman.h"
+
+void CNetRecvUnpacker::Clear()
+{
+	m_Valid = false;
+}
+
+void CNetRecvUnpacker::Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID)
+{
+	m_Addr = *pAddr;
+	m_pConnection = pConnection;
+	m_ClientID = ClientID;
+	m_CurrentChunk = 0;
+	m_Valid = true;
+}
+
+// TODO: rename this function
+int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk)
+{
+	CNetChunkHeader Header;
+	unsigned char *pEnd = m_Data.m_aChunkData + m_Data.m_DataSize;
+	
+	while(1)
+	{
+		unsigned char *pData = m_Data.m_aChunkData;
+		
+		// check for old data to unpack
+		if(!m_Valid || m_CurrentChunk >= m_Data.m_NumChunks)
+		{
+			Clear();
+			return 0;
+		}
+		
+		// TODO: add checking here so we don't read too far
+		for(int i = 0; i < m_CurrentChunk; i++)
+		{
+			pData = Header.Unpack(pData);
+			pData += Header.m_Size;
+		}
+		
+		// unpack the header
+		pData = Header.Unpack(pData);
+		m_CurrentChunk++;
+		
+		if(pData+Header.m_Size > pEnd)
+		{
+			Clear();
+			return 0;
+		}
+		
+		// handle sequence stuff
+		if(m_pConnection && (Header.m_Flags&NET_CHUNKFLAG_VITAL))
+		{
+			if(Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE)
+			{
+				// in sequence
+				m_pConnection->m_Ack = (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE;
+			}
+			else
+			{
+				// old packet that we already got
+				if(CNetBase::IsSeqInBackroom(Header.m_Sequence, m_pConnection->m_Ack))
+					continue;
+
+				// out of sequence, request resend
+				if(g_Config.m_Debug)
+					dbg_msg("conn", "asking for resend %d %d", Header.m_Sequence, (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE);
+				m_pConnection->SignalResend();
+				continue; // take the next chunk in the packet
+			}
+		}
+		
+		// fill in the info
+		pChunk->m_ClientID = m_ClientID;
+		pChunk->m_Address = m_Addr;
+		pChunk->m_Flags = 0;
+		pChunk->m_DataSize = Header.m_Size;
+		pChunk->m_pData = pData;
+		return 1;
+	}
+}
+
+// packs the data tight and sends it
+void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize)
+{
+	unsigned char aBuffer[NET_MAX_PACKETSIZE];
+	aBuffer[0] = 0xff;
+	aBuffer[1] = 0xff;
+	aBuffer[2] = 0xff;
+	aBuffer[3] = 0xff;
+	aBuffer[4] = 0xff;
+	aBuffer[5] = 0xff;
+	mem_copy(&aBuffer[6], pData, DataSize);
+	net_udp_send(Socket, pAddr, aBuffer, 6+DataSize);
+}
+
+void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket)
+{
+	unsigned char aBuffer[NET_MAX_PACKETSIZE];
+	int CompressedSize = -1;
+	int FinalSize = -1;
+
+	// log the data
+	if(ms_DataLogSent)
+	{
+		int Type = 1;
+		io_write(ms_DataLogSent, &Type, sizeof(Type));
+		io_write(ms_DataLogSent, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize));
+		io_write(ms_DataLogSent, &pPacket->m_aChunkData, pPacket->m_DataSize);
+		io_flush(ms_DataLogSent);
+	}
+	
+	// compress
+	CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4);
+
+	// check if the compression was enabled, successful and good enough
+	if(CompressedSize > 0 && CompressedSize < pPacket->m_DataSize)
+	{
+		FinalSize = CompressedSize;
+		pPacket->m_Flags |= NET_PACKETFLAG_COMPRESSION;
+	}
+	else
+	{
+		// use uncompressed data
+		FinalSize = pPacket->m_DataSize;
+		mem_copy(&aBuffer[3], pPacket->m_aChunkData, pPacket->m_DataSize);
+		pPacket->m_Flags &= ~NET_PACKETFLAG_COMPRESSION;
+	}
+
+	// set header and send the packet if all things are good
+	if(FinalSize >= 0)
+	{
+		FinalSize += NET_PACKETHEADERSIZE;
+		aBuffer[0] = ((pPacket->m_Flags<<4)&0xf0)|((pPacket->m_Ack>>8)&0xf);
+		aBuffer[1] = pPacket->m_Ack&0xff;
+		aBuffer[2] = pPacket->m_NumChunks;
+		net_udp_send(Socket, pAddr, aBuffer, FinalSize);
+
+		// log raw socket data
+		if(ms_DataLogSent)
+		{
+			int Type = 0;
+			io_write(ms_DataLogSent, &Type, sizeof(Type));
+			io_write(ms_DataLogSent, &FinalSize, sizeof(FinalSize));
+			io_write(ms_DataLogSent, aBuffer, FinalSize);
+			io_flush(ms_DataLogSent);
+		}
+	}
+}
+
+// TODO: rename this function
+int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket)
+{
+	// check the size
+	if(Size < NET_PACKETHEADERSIZE || Size > NET_MAX_PACKETSIZE)
+	{
+		dbg_msg("", "packet too small, %d", Size);
+		return -1;
+	}
+
+	// log the data
+	if(ms_DataLogRecv)
+	{
+		int Type = 0;
+		io_write(ms_DataLogRecv, &Type, sizeof(Type));
+		io_write(ms_DataLogRecv, &Size, sizeof(Size));
+		io_write(ms_DataLogRecv, pBuffer, Size);
+		io_flush(ms_DataLogRecv);
+	}
+	
+	// read the packet
+	pPacket->m_Flags = pBuffer[0]>>4;
+	pPacket->m_Ack = ((pBuffer[0]&0xf)<<8) | pBuffer[1];
+	pPacket->m_NumChunks = pBuffer[2];
+	pPacket->m_DataSize = Size - NET_PACKETHEADERSIZE;
+
+	if(pPacket->m_Flags&NET_PACKETFLAG_CONNLESS)
+	{
+		if(Size < 6)
+		{
+			dbg_msg("", "connection less packet too small, %d", Size);
+			return -1;
+		}
+			
+		pPacket->m_Flags = NET_PACKETFLAG_CONNLESS;
+		pPacket->m_Ack = 0;
+		pPacket->m_NumChunks = 0;
+		pPacket->m_DataSize = Size - 6;
+		mem_copy(pPacket->m_aChunkData, &pBuffer[6], pPacket->m_DataSize);
+	}
+	else
+	{
+		if(pPacket->m_Flags&NET_PACKETFLAG_COMPRESSION)
+			pPacket->m_DataSize = ms_Huffman.Decompress(&pBuffer[3], pPacket->m_DataSize, pPacket->m_aChunkData, sizeof(pPacket->m_aChunkData));
+		else
+			mem_copy(pPacket->m_aChunkData, &pBuffer[3], pPacket->m_DataSize);
+	}
+
+	// check for errors
+	if(pPacket->m_DataSize < 0)
+	{
+		if(g_Config.m_Debug)
+			dbg_msg("network", "error during packet decoding");
+		return -1;
+	}
+
+	// log the data
+	if(ms_DataLogRecv)
+	{
+		int Type = 1;
+		io_write(ms_DataLogRecv, &Type, sizeof(Type));
+		io_write(ms_DataLogRecv, &pPacket->m_DataSize, sizeof(pPacket->m_DataSize));
+		io_write(ms_DataLogRecv, pPacket->m_aChunkData, pPacket->m_DataSize);
+		io_flush(ms_DataLogRecv);
+	}
+		
+	// return success
+	return 0;
+}
+
+
+void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize)
+{
+	CNetPacketConstruct Construct;
+	Construct.m_Flags = NET_PACKETFLAG_CONTROL;
+	Construct.m_Ack = Ack;
+	Construct.m_NumChunks = 0;
+	Construct.m_DataSize = 1+ExtraSize;
+	Construct.m_aChunkData[0] = ControlMsg;
+	mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize);
+	
+	// send the control message
+	CNetBase::SendPacket(Socket, pAddr, &Construct);
+}
+
+
+
+unsigned char *CNetChunkHeader::Pack(unsigned char *pData)
+{
+	pData[0] = ((m_Flags&3)<<6)|((m_Size>>4)&0x3f);
+	pData[1] = (m_Size&0xf);
+	if(m_Flags&NET_CHUNKFLAG_VITAL)
+	{
+		pData[1] |= (m_Sequence>>2)&0xf0;
+		pData[2] = m_Sequence&0xff;
+		return pData + 3;
+	}
+	return pData + 2;
+}
+
+unsigned char *CNetChunkHeader::Unpack(unsigned char *pData)
+{
+	m_Flags = (pData[0]>>6)&3;
+	m_Size = ((pData[0]&0x3f)<<4) | (pData[1]&0xf);
+	m_Sequence = -1;
+	if(m_Flags&NET_CHUNKFLAG_VITAL)
+	{
+		m_Sequence = ((pData[1]&0xf0)<<2) | pData[2];
+		return pData + 3;
+	}
+	return pData + 2;
+}
+
+
+int CNetBase::IsSeqInBackroom(int Seq, int Ack)
+{
+	int Bottom = (Ack-NET_MAX_SEQUENCE/2);
+	if(Bottom < 0)
+	{
+		if(Seq <= Ack)
+			return 1;
+		if(Seq >= (Bottom + NET_MAX_SEQUENCE))
+			return 1;
+	}
+	else
+	{
+		if(Seq <= Ack && Seq >= Bottom)
+			return 1;
+	}
+	
+	return 0;
+}
+
+IOHANDLE CNetBase::ms_DataLogSent = 0;
+IOHANDLE CNetBase::ms_DataLogRecv = 0;
+CHuffman CNetBase::ms_Huffman;
+
+
+void CNetBase::OpenLog(const char *pSentLog, const char *pRecvLog)
+{
+	/*
+	if(pSentLog)
+	{
+		ms_DataLogSent = engine_openfile(pSentLog, IOFLAG_WRITE);
+		if(ms_DataLogSent)
+			dbg_msg("network", "logging sent packages to '%s'", pSentLog);
+		else
+			dbg_msg("network", "failed to open for logging '%s'", pSentLog);
+	}
+	
+	if(pRecvLog)
+	{
+		ms_DataLogRecv = engine_openfile(pRecvLog, IOFLAG_WRITE);
+		if(ms_DataLogRecv)
+			dbg_msg("network", "logging recv packages to '%s'", pRecvLog);
+		else
+			dbg_msg("network", "failed to open for logging '%s'", pRecvLog);
+	}*/
+}
+
+int CNetBase::Compress(const void *pData, int DataSize, void *pOutput, int OutputSize)
+{
+	return ms_Huffman.Compress(pData, DataSize, pOutput, OutputSize);
+}
+
+int CNetBase::Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize)
+{
+	return ms_Huffman.Decompress(pData, DataSize, pOutput, OutputSize);
+}
+
+
+static const unsigned gs_aFreqTable[256+1] = {
+	1<<30,4545,2657,431,1950,919,444,482,2244,617,838,542,715,1814,304,240,754,212,647,186,
+	283,131,146,166,543,164,167,136,179,859,363,113,157,154,204,108,137,180,202,176,
+	872,404,168,134,151,111,113,109,120,126,129,100,41,20,16,22,18,18,17,19,
+	16,37,13,21,362,166,99,78,95,88,81,70,83,284,91,187,77,68,52,68,
+	59,66,61,638,71,157,50,46,69,43,11,24,13,19,10,12,12,20,14,9,
+	20,20,10,10,15,15,12,12,7,19,15,14,13,18,35,19,17,14,8,5,
+	15,17,9,15,14,18,8,10,2173,134,157,68,188,60,170,60,194,62,175,71,
+	148,67,167,78,211,67,156,69,1674,90,174,53,147,89,181,51,174,63,163,80,
+	167,94,128,122,223,153,218,77,200,110,190,73,174,69,145,66,277,143,141,60,
+	136,53,180,57,142,57,158,61,166,112,152,92,26,22,21,28,20,26,30,21,
+	32,27,20,17,23,21,30,22,22,21,27,25,17,27,23,18,39,26,15,21,
+	12,18,18,27,20,18,15,19,11,17,33,12,18,15,19,18,16,26,17,18,
+	9,10,25,22,22,17,20,16,6,16,15,20,14,18,24,335,1517};
+
+void CNetBase::Init()
+{
+	ms_Huffman.Init(gs_aFreqTable);
+}
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
new file mode 100644
index 00000000..11a1b70d
--- /dev/null
+++ b/src/engine/shared/network.h
@@ -0,0 +1,346 @@
+#ifndef ENGINE_SHARED_NETWORK_H
+#define ENGINE_SHARED_NETWORK_H
+
+#include "ringbuffer.h"
+#include "huffman.h"
+
+/*
+
+CURRENT:
+	packet header: 3 bytes
+		unsigned char flags_ack; // 4bit flags, 4bit ack
+		unsigned char ack; // 8 bit ack
+		unsigned char num_chunks; // 8 bit chunks
+		
+		(unsigned char padding[3])	// 24 bit extra incase it's a connection less packet
+									// this is to make sure that it's compatible with the
+									// old protocol
+
+	chunk header: 2-3 bytes
+		unsigned char flags_size; // 2bit flags, 6 bit size
+		unsigned char size_seq; // 4bit size, 4bit seq
+		(unsigned char seq;) // 8bit seq, if vital flag is set
+*/
+
+enum
+{
+	NETFLAG_ALLOWSTATELESS=1,
+	NETSENDFLAG_VITAL=1,
+	NETSENDFLAG_CONNLESS=2,
+	NETSENDFLAG_FLUSH=4,
+	
+	NETSTATE_OFFLINE=0,
+	NETSTATE_CONNECTING,
+	NETSTATE_ONLINE,
+	
+	NETBANTYPE_SOFT=1,
+	NETBANTYPE_DROP=2
+};
+
+
+enum
+{
+	NET_VERSION = 2,
+
+	NET_MAX_CHUNKSIZE = 1024,
+	NET_MAX_PAYLOAD = NET_MAX_CHUNKSIZE+16,
+	NET_MAX_PACKETSIZE = NET_MAX_PAYLOAD+16,
+	NET_MAX_CHUNKHEADERSIZE = 5,
+	NET_PACKETHEADERSIZE = 3,
+	NET_MAX_CLIENTS = 16,
+	NET_MAX_SEQUENCE = 1<<10,
+	NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1,
+
+	NET_CONNSTATE_OFFLINE=0,
+	NET_CONNSTATE_CONNECT=1,
+	NET_CONNSTATE_PENDING=2,
+	NET_CONNSTATE_ONLINE=3,
+	NET_CONNSTATE_ERROR=4,
+
+	NET_PACKETFLAG_CONTROL=1,
+	NET_PACKETFLAG_CONNLESS=2,
+	NET_PACKETFLAG_RESEND=4,
+	NET_PACKETFLAG_COMPRESSION=8,
+
+	NET_CHUNKFLAG_VITAL=1,
+	NET_CHUNKFLAG_RESEND=2,
+	
+	NET_CTRLMSG_KEEPALIVE=0,
+	NET_CTRLMSG_CONNECT=1,
+	NET_CTRLMSG_CONNECTACCEPT=2,
+	NET_CTRLMSG_ACCEPT=3,
+	NET_CTRLMSG_CLOSE=4,
+	
+	NET_SERVER_MAXBANS=1024,
+	
+	NET_CONN_BUFFERSIZE=1024*16,
+	
+	NET_ENUM_TERMINATOR
+};
+
+
+typedef int (*NETFUNC_DELCLIENT)(int ClientID, void *pUser);
+typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser);
+
+struct CNetChunk
+{
+	// -1 means that it's a stateless packet
+	// 0 on the client means the server
+	int m_ClientID;
+	NETADDR m_Address; // only used when client_id == -1
+	int m_Flags;
+	int m_DataSize;
+	const void *m_pData;
+};
+
+class CNetChunkHeader
+{
+public:
+	int m_Flags;
+	int m_Size;
+	int m_Sequence;
+	
+	unsigned char *Pack(unsigned char *pData);
+	unsigned char *Unpack(unsigned char *pData);
+};
+
+class CNetChunkResend
+{
+public:
+	int m_Flags;
+	int m_DataSize;
+	unsigned char *m_pData;
+
+	int m_Sequence;
+	int64 m_LastSendTime;
+	int64 m_FirstSendTime;
+};
+
+class CNetPacketConstruct
+{
+public:
+	int m_Flags;
+	int m_Ack;
+	int m_NumChunks;
+	int m_DataSize;
+	unsigned char m_aChunkData[NET_MAX_PAYLOAD];
+};
+
+
+class CNetConnection
+{
+	// TODO: is this needed because this needs to be aware of
+	// the ack sequencing number and is also responible for updating
+	// that. this should be fixed.
+	friend class CNetRecvUnpacker;
+private:
+	unsigned short m_Sequence;
+	unsigned short m_Ack;
+	unsigned m_State;
+	
+	int m_Token;
+	int m_RemoteClosed;
+	
+	TStaticRingBuffer<CNetChunkResend, NET_CONN_BUFFERSIZE> m_Buffer;
+	
+	int64 m_LastUpdateTime;
+	int64 m_LastRecvTime;
+	int64 m_LastSendTime;
+	
+	char m_ErrorString[256];
+	
+	CNetPacketConstruct m_Construct;
+	
+	NETADDR m_PeerAddr;
+	NETSOCKET m_Socket;
+	NETSTATS m_Stats;
+	
+	//
+	void Reset();
+	void ResetStats();
+	void SetError(const char *pString);
+	void AckChunks(int Ack);
+	
+	int QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence);
+	void SendControl(int ControlMsg, const void *pExtra, int ExtraSize);
+	void ResendChunk(CNetChunkResend *pResend);
+	void Resend();
+
+public:
+	void Init(NETSOCKET Socket);
+	int Connect(NETADDR *pAddr);
+	void Disconnect(const char *pReason);
+
+	int Update();
+	int Flush();	
+
+	int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr);
+	int QueueChunk(int Flags, int DataSize, const void *pData);
+
+	const char *ErrorString();
+	void SignalResend();
+	int State() const { return m_State; }
+	NETADDR PeerAddress() const { return m_PeerAddr; }
+	
+	void ResetErrorString() { m_ErrorString[0] = 0; }
+	const char *ErrorString() const { return m_ErrorString; }
+	
+	// Needed for GotProblems in NetClient
+	int64 LastRecvTime() const { return m_LastRecvTime; }
+	
+	int AckSequence() const { return m_Ack; }
+};
+
+struct CNetRecvUnpacker
+{
+public:
+	bool m_Valid;
+	
+	NETADDR m_Addr;
+	CNetConnection *m_pConnection;
+	int m_CurrentChunk;
+	int m_ClientID;
+	CNetPacketConstruct m_Data;
+	unsigned char m_aBuffer[NET_MAX_PACKETSIZE];
+
+	CNetRecvUnpacker() { Clear(); }
+	void Clear();
+	void Start(const NETADDR *pAddr, CNetConnection *pConnection, int ClientID);
+	int FetchChunk(CNetChunk *pChunk);	
+};
+
+// server side
+class CNetServer
+{
+public:
+	struct CBanInfo
+	{
+		NETADDR m_Addr;
+		int m_Expires;
+	};
+	
+private:
+	class CSlot
+	{
+	public:
+		CNetConnection m_Connection;
+	};
+	
+	class CBan
+	{
+	public:
+		CBanInfo m_Info;
+		
+		// hash list
+		CBan *m_pHashNext;
+		CBan *m_pHashPrev;
+		
+		// used or free list
+		CBan *m_pNext;
+		CBan *m_pPrev;
+	};
+	
+	
+	NETSOCKET m_Socket;
+	CSlot m_aSlots[NET_MAX_CLIENTS];
+	int m_MaxClients;
+
+	CBan *m_aBans[256];
+	CBan m_BanPool[NET_SERVER_MAXBANS];
+	CBan *m_BanPool_FirstFree;
+	CBan *m_BanPool_FirstUsed;
+
+	NETFUNC_NEWCLIENT m_pfnNewClient;
+	NETFUNC_DELCLIENT m_pfnDelClient;
+	void *m_UserPtr;
+	
+	CNetRecvUnpacker m_RecvUnpacker;
+	
+	void BanRemoveByObject(CBan *pBan);
+	
+public:
+	int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+
+	//
+	bool Open(NETADDR BindAddr, int MaxClients, int Flags);
+	int Close();
+	
+	//
+	int Recv(CNetChunk *pChunk);
+	int Send(CNetChunk *pChunk);
+	int Update();
+	
+	//
+	int Drop(int ClientID, const char *Reason);
+
+	// banning
+	int BanAdd(NETADDR Addr, int Seconds);
+	int BanRemove(NETADDR Addr);
+	int BanNum(); // caution, slow
+	int BanGet(int Index, CBanInfo *pInfo); // caution, slow
+
+	// status requests
+	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	NETSOCKET Socket() const { return m_Socket; }
+	int MaxClients() const { return m_MaxClients; }
+};
+
+
+
+// client side
+class CNetClient
+{
+	NETADDR m_ServerAddr;
+	CNetConnection m_Connection;
+	CNetRecvUnpacker m_RecvUnpacker;
+	NETSOCKET m_Socket;
+public:
+	// openness
+	bool Open(NETADDR BindAddr, int Flags);
+	int Close();
+	
+	// connection state
+	int Disconnect(const char *Reason);
+	int Connect(NETADDR *Addr);
+	
+	// communication
+	int Recv(CNetChunk *Chunk);
+	int Send(CNetChunk *Chunk);
+	
+	// pumping
+	int Update();
+	int Flush();
+
+	int ResetErrorString();
+	
+	// error and state
+	int State();
+	int GotProblems();
+	const char *ErrorString();
+};
+
+
+
+// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere
+class CNetBase
+{
+	static IOHANDLE ms_DataLogSent;
+	static IOHANDLE ms_DataLogRecv;
+	static CHuffman ms_Huffman;
+public:
+	static void OpenLog(const char *pSentlog, const char *pRecvlog);
+	static void Init();
+	static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize);
+	static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize);
+	
+	static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize);
+	static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize);
+	static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket);
+	static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket);
+
+	// The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not
+	static int IsSeqInBackroom(int Seq, int Ack);	
+};
+
+
+#endif
diff --git a/src/engine/shared/network_client.cpp b/src/engine/shared/network_client.cpp
new file mode 100644
index 00000000..f7859c0a
--- /dev/null
+++ b/src/engine/shared/network_client.cpp
@@ -0,0 +1,139 @@
+#include <base/system.h>
+#include "network.h"
+
+bool CNetClient::Open(NETADDR BindAddr, int Flags)
+{
+	// clean it
+	mem_zero(this, sizeof(*this));
+
+	// open socket
+	m_Socket = net_udp_create(BindAddr);
+	m_Connection.Init(m_Socket);
+	return true;
+}
+
+int CNetClient::Close()
+{
+	// TODO: implement me
+	return 0;
+}
+
+
+int CNetClient::Disconnect(const char *pReason)
+{
+	dbg_msg("netclient", "disconnected. reason=\"%s\"", pReason);
+	m_Connection.Disconnect(pReason);
+	return 0;
+}
+
+int CNetClient::Update()
+{
+	m_Connection.Update();
+	if(m_Connection.State() == NET_CONNSTATE_ERROR)
+		Disconnect(m_Connection.ErrorString());
+	return 0;
+}
+
+int CNetClient::Connect(NETADDR *pAddr)
+{
+	m_Connection.Connect(pAddr);
+	return 0;
+}
+
+int CNetClient::ResetErrorString()
+{
+	m_Connection.ResetErrorString();
+	return 0;
+}
+
+int CNetClient::Recv(CNetChunk *pChunk)
+{
+	while(1)
+	{
+		// check for a chunk
+		if(m_RecvUnpacker.FetchChunk(pChunk))
+			return 1;
+		
+		// TODO: empty the recvinfo
+		NETADDR Addr;
+		int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE);
+
+		// no more packets for now
+		if(Bytes <= 0)
+			break;
+
+		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
+		{
+			if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS)
+			{
+				pChunk->m_Flags = NETSENDFLAG_CONNLESS;
+				pChunk->m_ClientID = -1;
+				pChunk->m_Address = Addr;
+				pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize;
+				pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData;
+				return 1;
+			}
+			else
+			{
+				if(m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
+					m_RecvUnpacker.Start(&Addr, &m_Connection, 0);
+			}
+		}
+	}
+	return 0;
+}
+
+int CNetClient::Send(CNetChunk *pChunk)
+{
+	if(pChunk->m_DataSize >= NET_MAX_PAYLOAD)
+	{
+		dbg_msg("netclient", "chunk payload too big. %d. dropping chunk", pChunk->m_DataSize);
+		return -1;
+	}
+	
+	if(pChunk->m_Flags&NETSENDFLAG_CONNLESS)
+	{
+		// send connectionless packet
+		CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize);
+	}
+	else
+	{
+		int Flags = 0;
+		dbg_assert(pChunk->m_ClientID == 0, "errornous client id");
+		
+		if(pChunk->m_Flags&NETSENDFLAG_VITAL)
+			Flags = NET_CHUNKFLAG_VITAL;
+		
+		m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData);
+
+		if(pChunk->m_Flags&NETSENDFLAG_FLUSH)
+			m_Connection.Flush();
+	}
+	return 0;
+}
+
+int CNetClient::State()
+{
+	if(m_Connection.State() == NET_CONNSTATE_ONLINE)
+		return NETSTATE_ONLINE;
+	if(m_Connection.State() == NET_CONNSTATE_OFFLINE)
+		return NETSTATE_OFFLINE;
+	return NETSTATE_CONNECTING;
+}
+
+int CNetClient::Flush()
+{
+	return m_Connection.Flush();
+}
+
+int CNetClient::GotProblems()
+{
+	if(time_get() - m_Connection.LastRecvTime() > time_freq())
+		return 1;
+	return 0;
+}
+
+const char *CNetClient::ErrorString()
+{
+	return m_Connection.ErrorString();
+}
diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp
new file mode 100644
index 00000000..4ed157eb
--- /dev/null
+++ b/src/engine/shared/network_conn.cpp
@@ -0,0 +1,354 @@
+#include <base/system.h>
+#include "config.h"
+#include "network.h"
+
+void CNetConnection::ResetStats()
+{
+	mem_zero(&m_Stats, sizeof(m_Stats));
+}
+
+void CNetConnection::Reset()
+{
+	m_Sequence = 0;
+	m_Ack = 0;
+	m_RemoteClosed = 0;
+	
+	m_State = NET_CONNSTATE_OFFLINE;
+	m_LastSendTime = 0;
+	m_LastRecvTime = 0;
+	m_LastUpdateTime = 0;
+	m_Token = -1;
+	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+	
+	m_Buffer.Init();
+	
+	mem_zero(&m_Construct, sizeof(m_Construct));
+}
+
+const char *CNetConnection::ErrorString()
+{
+	return m_ErrorString;
+}
+
+void CNetConnection::SetError(const char *pString)
+{
+	str_copy(m_ErrorString, pString, sizeof(m_ErrorString));
+}
+
+void CNetConnection::Init(NETSOCKET Socket)
+{
+	Reset();
+	ResetStats();
+	
+	m_Socket = Socket;
+	mem_zero(m_ErrorString, sizeof(m_ErrorString));
+}
+
+void CNetConnection::AckChunks(int Ack)
+{
+	while(1)
+	{
+		CNetChunkResend *pResend = m_Buffer.First();
+		if(!pResend)
+			break;
+		
+		if(CNetBase::IsSeqInBackroom(pResend->m_Sequence, Ack))
+			m_Buffer.PopFirst();
+		else
+			break;
+	}
+}
+
+void CNetConnection::SignalResend()
+{
+	m_Construct.m_Flags |= NET_PACKETFLAG_RESEND;
+}
+
+int CNetConnection::Flush()
+{
+	int NumChunks = m_Construct.m_NumChunks;
+	if(!NumChunks && !m_Construct.m_Flags)
+		return 0;
+
+	// send of the packets
+	m_Construct.m_Ack = m_Ack;
+	CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct);
+	
+	// update send times
+	m_LastSendTime = time_get();
+	
+	// clear construct so we can start building a new package
+	mem_zero(&m_Construct, sizeof(m_Construct));
+	return NumChunks;
+}
+
+int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence)
+{
+	unsigned char *pChunkData;
+	
+	// check if we have space for it, if not, flush the connection
+	if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData))
+		Flush();
+
+	// pack all the data
+	CNetChunkHeader Header;
+	Header.m_Flags = Flags;
+	Header.m_Size = DataSize;
+	Header.m_Sequence = Sequence;
+	pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize];
+	pChunkData = Header.Pack(pChunkData);
+	mem_copy(pChunkData, pData, DataSize);
+	pChunkData += DataSize;
+
+	//
+	m_Construct.m_NumChunks++;
+	m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData);
+	
+	// set packet flags aswell
+	
+	if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND))
+	{
+		// save packet if we need to resend
+		CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize);
+		if(pResend)
+		{
+			pResend->m_Sequence = Sequence;
+			pResend->m_Flags = Flags;
+			pResend->m_DataSize = DataSize;
+			pResend->m_pData = (unsigned char *)(pResend+1);
+			pResend->m_FirstSendTime = time_get();
+			pResend->m_LastSendTime = pResend->m_FirstSendTime;
+			mem_copy(pResend->m_pData, pData, DataSize);
+		}
+		else
+		{
+			// out of buffer
+			Disconnect("too weak connection (out of buffer)");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData)
+{
+	if(Flags&NET_CHUNKFLAG_VITAL)
+		m_Sequence = (m_Sequence+1)%NET_MAX_SEQUENCE;
+	return QueueChunkEx(Flags, DataSize, pData, m_Sequence);
+}
+
+void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSize)
+{
+	// send the control message
+	m_LastSendTime = time_get();
+	CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize);
+}
+
+void CNetConnection::ResendChunk(CNetChunkResend *pResend)
+{
+	QueueChunkEx(pResend->m_Flags|NET_CHUNKFLAG_RESEND, pResend->m_DataSize, pResend->m_pData, pResend->m_Sequence);
+	pResend->m_LastSendTime = time_get();
+}
+
+void CNetConnection::Resend()
+{
+	for(CNetChunkResend *pResend = m_Buffer.First(); pResend; pResend = m_Buffer.Next(pResend))
+		ResendChunk(pResend);
+}
+
+int CNetConnection::Connect(NETADDR *pAddr)
+{
+	if(State() != NET_CONNSTATE_OFFLINE)
+		return -1;
+	
+	// init connection
+	Reset();
+	m_PeerAddr = *pAddr;
+	mem_zero(m_ErrorString, sizeof(m_ErrorString));
+	m_State = NET_CONNSTATE_CONNECT;
+	SendControl(NET_CTRLMSG_CONNECT, 0, 0);
+	return 0;
+}
+
+void CNetConnection::Disconnect(const char *pReason)
+{
+	if(State() == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(m_RemoteClosed == 0)
+	{
+		if(pReason)
+			SendControl(NET_CTRLMSG_CLOSE, pReason, str_length(pReason)+1);
+		else
+			SendControl(NET_CTRLMSG_CLOSE, 0, 0);
+
+		m_ErrorString[0] = 0;
+		if(pReason)
+			str_copy(m_ErrorString, pReason, sizeof(m_ErrorString));
+	}
+	
+	Reset();
+}
+
+int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
+{
+	int64 Now = time_get();
+	m_LastRecvTime = Now;
+	
+	// check if resend is requested
+	if(pPacket->m_Flags&NET_PACKETFLAG_RESEND)
+		Resend();
+
+	//
+	if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL)
+	{
+		int CtrlMsg = pPacket->m_aChunkData[0];
+		
+		if(CtrlMsg == NET_CTRLMSG_CLOSE)
+		{
+			if(net_addr_comp(&m_PeerAddr, pAddr) == 0)
+			{
+				m_State = NET_CONNSTATE_ERROR;
+				m_RemoteClosed = 1;
+				
+				if(pPacket->m_DataSize)
+				{
+					// make sure to sanitize the error string form the other party
+					char Str[128];
+					if(pPacket->m_DataSize < 128)
+						str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize);
+					else
+						str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str));
+					str_sanitize_strong(Str);
+					
+					// set the error string
+					SetError(Str);
+				}
+				else
+					SetError("no reason given");
+					
+				if(g_Config.m_Debug)
+					dbg_msg("conn", "closed reason='%s'", ErrorString());
+			}
+			return 0;			
+		}
+		else
+		{
+			if(State() == NET_CONNSTATE_OFFLINE)
+			{
+				if(CtrlMsg == NET_CTRLMSG_CONNECT)
+				{
+					// send response and init connection
+					Reset();
+					m_State = NET_CONNSTATE_PENDING;
+					m_PeerAddr = *pAddr;
+					m_LastSendTime = Now;
+					m_LastRecvTime = Now;
+					m_LastUpdateTime = Now;
+					SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+					if(g_Config.m_Debug)
+						dbg_msg("connection", "got connection, sending connect+accept");			
+				}
+			}
+			else if(State() == NET_CONNSTATE_CONNECT)
+			{
+				// connection made
+				if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
+					m_State = NET_CONNSTATE_ONLINE;
+					if(g_Config.m_Debug)
+						dbg_msg("connection", "got connect+accept, sending accept. connection online");
+				}
+			}
+			else if(State() == NET_CONNSTATE_ONLINE)
+			{
+				// connection made
+				/*
+				if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					
+				}*/
+			}
+		}
+	}
+	else
+	{
+		if(State() == NET_CONNSTATE_PENDING)
+		{
+			m_State = NET_CONNSTATE_ONLINE;
+			if(g_Config.m_Debug)
+				dbg_msg("connection", "connecting online");
+		}
+	}
+	
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		AckChunks(pPacket->m_Ack);
+	}
+	
+	return 1;
+}
+
+int CNetConnection::Update()
+{
+	int64 Now = time_get();
+
+	if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR)
+		return 0;
+	
+	// check for timeout
+	if(State() != NET_CONNSTATE_OFFLINE &&
+		State() != NET_CONNSTATE_CONNECT &&
+		(Now-m_LastRecvTime) > time_freq()*10)
+	{
+		m_State = NET_CONNSTATE_ERROR;
+		SetError("timeout");
+	}
+
+	// fix resends
+	if(m_Buffer.First())
+	{
+		CNetChunkResend *pResend = m_Buffer.First();
+
+		// check if we have some really old stuff laying around and abort if not acked
+		if(Now-pResend->m_FirstSendTime > time_freq()*10)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			SetError("too weak connection (not acked for 10 seconds)");
+		}
+		else
+		{
+			// resend packet if we havn't got it acked in 1 second
+			if(Now-pResend->m_LastSendTime > time_freq())
+				ResendChunk(pResend);
+		}
+	}
+	
+	// send keep alives if nothing has happend for 250ms
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) // flush connection after 500ms if needed
+		{
+			int NumFlushedChunks = Flush();
+			if(NumFlushedChunks && g_Config.m_Debug)
+				dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks);
+		}
+			
+		if(time_get()-m_LastSendTime > time_freq())
+			SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
+	}
+	else if(State() == NET_CONNSTATE_CONNECT)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect every 500ms
+			SendControl(NET_CTRLMSG_CONNECT, 0, 0);
+	}
+	else if(State() == NET_CONNSTATE_PENDING)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect/accept every 500ms
+			SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+	}
+	
+	return 0;
+}
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
new file mode 100644
index 00000000..32b08bf6
--- /dev/null
+++ b/src/engine/shared/network_server.cpp
@@ -0,0 +1,413 @@
+#include <base/system.h>
+#include "network.h"
+
+#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \
+	{ if(First) First->Prev = Object; \
+	Object->Prev = (struct CBan *)0; \
+	Object->Next = First; \
+	First = Object; }
+	
+#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \
+	{ Object->Prev = After; \
+	Object->Next = After->Next; \
+	After->Next = Object; \
+	if(Object->Next) \
+		Object->Next->Prev = Object; \
+	}
+
+#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \
+	{ if(Object->Next) Object->Next->Prev = Object->Prev; \
+	if(Object->Prev) Object->Prev->Next = Object->Next; \
+	else First = Object->Next; \
+	Object->Next = 0; Object->Prev = 0; }
+	
+#define MACRO_LIST_FIND(Start, Next, Expression) \
+	{ while(Start && !(Expression)) Start = Start->Next; }
+
+bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int Flags)
+{
+	// zero out the whole structure
+	mem_zero(this, sizeof(*this));
+	
+	// open socket
+	m_Socket = net_udp_create(BindAddr);
+	if(m_Socket == NETSOCKET_INVALID)
+		return false;
+	
+	// clamp clients
+	m_MaxClients = MaxClients;
+	if(m_MaxClients > NET_MAX_CLIENTS)
+		m_MaxClients = NET_MAX_CLIENTS;
+	if(m_MaxClients < 1)
+		m_MaxClients = 1;
+	
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Init(m_Socket);
+	
+	// setup all pointers for bans
+	for(int i = 1; i < NET_SERVER_MAXBANS-1; i++)
+	{
+		m_BanPool[i].m_pNext = &m_BanPool[i+1];
+		m_BanPool[i].m_pPrev = &m_BanPool[i-1];
+	}
+	
+	m_BanPool[0].m_pNext = &m_BanPool[1];
+	m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2];
+	m_BanPool_FirstFree = &m_BanPool[0];
+
+	return true;
+}
+
+int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+	return 0;
+}
+
+int CNetServer::Close()
+{
+	// TODO: implement me
+	return 0;
+}
+
+int CNetServer::Drop(int ClientID, const char *pReason)
+{
+	// TODO: insert lots of checks here
+	NETADDR Addr = ClientAddr(ClientID);
+
+	dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"",
+		ClientID,
+		Addr.ip[0], Addr.ip[1], Addr.ip[2], Addr.ip[3],
+		pReason
+		);
+		
+	m_aSlots[ClientID].m_Connection.Disconnect(pReason);
+
+	if(m_pfnDelClient)
+		m_pfnDelClient(ClientID, m_UserPtr);
+		
+	return 0;
+}
+
+int CNetServer::BanGet(int Index, CBanInfo *pInfo)
+{
+	CBan *pBan;
+	for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--)
+		{}
+		
+	if(!pBan)
+		return 0;
+	*pInfo = pBan->m_Info;
+	return 1;
+}
+
+int CNetServer::BanNum()
+{
+	int Count = 0;
+	CBan *pBan;
+	for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext)
+		Count++;
+	return Count;
+}
+
+void CNetServer::BanRemoveByObject(CBan *pBan)
+{
+	int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3])&0xff;
+	dbg_msg("netserver", "removing ban on %d.%d.%d.%d",
+		pBan->m_Info.m_Addr.ip[0], pBan->m_Info.m_Addr.ip[1], pBan->m_Info.m_Addr.ip[2], pBan->m_Info.m_Addr.ip[3]);
+	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+	MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
+	MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
+}
+
+int CNetServer::BanRemove(NETADDR Addr)
+{
+	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff;
+	CBan *pBan = m_aBans[IpHash];
+	
+	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
+	
+	if(pBan)
+	{
+		BanRemoveByObject(pBan);
+		return 0;
+	}
+	
+	return -1;
+}
+
+int CNetServer::BanAdd(NETADDR Addr, int Seconds)
+{
+	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3])&0xff;
+	int Stamp = -1;
+	CBan *pBan;
+	
+	// remove the port
+	Addr.port = 0;
+	
+	if(Seconds)
+		Stamp = time_timestamp() + Seconds;
+		
+	// search to see if it already exists
+	pBan = m_aBans[IpHash];
+	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
+	if(pBan)
+	{
+		// adjust the ban
+		pBan->m_Info.m_Expires = Stamp;
+		return 0;
+	}
+	
+	if(!m_BanPool_FirstFree)
+		return -1;
+
+	// fetch and clear the new ban
+	pBan = m_BanPool_FirstFree;
+	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
+	
+	// setup the ban info
+	pBan->m_Info.m_Expires = Stamp;
+	pBan->m_Info.m_Addr = Addr;
+	
+	// add it to the ban hash
+	MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
+	
+	// insert it into the used list
+	{
+		if(m_BanPool_FirstUsed)
+		{
+			CBan *pInsertAfter = m_BanPool_FirstUsed;
+			MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires);
+			
+			if(pInsertAfter)
+				pInsertAfter = pInsertAfter->m_pPrev;
+			else
+			{
+				// add to last
+				pInsertAfter = m_BanPool_FirstUsed;
+				while(pInsertAfter->m_pNext)
+					pInsertAfter = pInsertAfter->m_pNext;
+			}
+			
+			if(pInsertAfter)
+			{
+				MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext);
+			}
+			else
+			{
+				MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+			}
+		}
+		else
+		{
+			MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
+		}
+	}
+
+	// drop banned clients
+	{
+		char Buf[128];
+		NETADDR BanAddr;
+		
+		if(Seconds)
+			str_format(Buf, sizeof(Buf), "you have been banned for %d minutes", Seconds/60);
+		else
+			str_format(Buf, sizeof(Buf), "you have been banned for life");
+		
+		for(int i = 0; i < MaxClients(); i++)
+		{
+			BanAddr = m_aSlots[i].m_Connection.PeerAddress();
+			BanAddr.port = 0;
+			
+			if(net_addr_comp(&Addr, &BanAddr) == 0)
+				Drop(i, Buf);
+		}
+	}
+	return 0;
+}
+
+int CNetServer::Update()
+{
+	int Now = time_timestamp();
+	for(int i = 0; i < MaxClients(); i++)
+	{
+		m_aSlots[i].m_Connection.Update();
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
+	}
+	
+	// remove expired bans
+	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
+	{
+		CBan *pBan = m_BanPool_FirstUsed;
+		BanRemoveByObject(pBan);
+	}
+	
+	return 0;
+}
+
+/*
+	TODO: chopp up this function into smaller working parts
+*/
+int CNetServer::Recv(CNetChunk *pChunk)
+{
+	unsigned Now = time_timestamp();
+	
+	while(1)
+	{
+		NETADDR Addr;
+			
+		// check for a chunk
+		if(m_RecvUnpacker.FetchChunk(pChunk))
+			return 1;
+		
+		// TODO: empty the recvinfo
+		int Bytes = net_udp_recv(m_Socket, &Addr, m_RecvUnpacker.m_aBuffer, NET_MAX_PACKETSIZE);
+
+		// no more packets for now
+		if(Bytes <= 0)
+			break;
+		
+		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
+		{
+			CBan *pBan = 0;
+			NETADDR BanAddr = Addr;
+			int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3])&0xff;
+			int Found = 0;
+			BanAddr.port = 0;
+			
+			// search a ban
+			for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext)
+			{
+				if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0)
+					break;
+			}
+			
+			// check if we just should drop the packet
+			if(pBan)
+			{
+				// banned, reply with a message
+				char BanStr[128];
+				if(pBan->m_Info.m_Expires)
+				{
+					int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60;
+					if(Mins == 1)
+						str_format(BanStr, sizeof(BanStr), "banned for %d minute", Mins);
+					else
+						str_format(BanStr, sizeof(BanStr), "banned for %d minutes", Mins);
+				}
+				else
+					str_format(BanStr, sizeof(BanStr), "banned for life");
+				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1);
+				continue;
+			}
+			
+			if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS)
+			{
+				pChunk->m_Flags = NETSENDFLAG_CONNLESS;
+				pChunk->m_ClientID = -1;
+				pChunk->m_Address = Addr;
+				pChunk->m_DataSize = m_RecvUnpacker.m_Data.m_DataSize;
+				pChunk->m_pData = m_RecvUnpacker.m_Data.m_aChunkData;
+				return 1;
+			}
+			else
+			{			
+				// TODO: check size here
+				if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
+				{
+					Found = 0;
+				
+					// check if we already got this client
+					for(int i = 0; i < MaxClients(); i++)
+					{
+						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+						if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
+							net_addr_comp(&PeerAddr, &Addr) == 0)
+						{
+							Found = 1; // silent ignore.. we got this client already
+							break;
+						}
+					}
+					
+					// client that wants to connect
+					if(!Found)
+					{
+						for(int i = 0; i < MaxClients(); i++)
+						{
+							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+							{
+								Found = 1;
+								m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
+								if(m_pfnNewClient)
+									m_pfnNewClient(i, m_UserPtr);
+								break;
+							}
+						}
+						
+						if(!Found)
+						{
+							const char FullMsg[] = "server is full";
+							CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg));
+						}
+					}
+				}
+				else
+				{
+					// normal packet, find matching slot
+					for(int i = 0; i < MaxClients(); i++)
+					{
+						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+						if(net_addr_comp(&PeerAddr, &Addr) == 0)
+						{
+							if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
+							{
+								if(m_RecvUnpacker.m_Data.m_DataSize)
+									m_RecvUnpacker.Start(&Addr, &m_aSlots[i].m_Connection, i);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+int CNetServer::Send(CNetChunk *pChunk)
+{
+	if(pChunk->m_DataSize >= NET_MAX_PAYLOAD)
+	{
+		dbg_msg("netserver", "packet payload too big. %d. dropping packet", pChunk->m_DataSize);
+		return -1;
+	}
+	
+	if(pChunk->m_Flags&NETSENDFLAG_CONNLESS)
+	{
+		// send connectionless packet
+		CNetBase::SendPacketConnless(m_Socket, &pChunk->m_Address, pChunk->m_pData, pChunk->m_DataSize);
+	}
+	else
+	{
+		int Flags = 0;
+		dbg_assert(pChunk->m_ClientID >= 0, "errornous client id");
+		dbg_assert(pChunk->m_ClientID < MaxClients(), "errornous client id");
+		
+		if(pChunk->m_Flags&NETSENDFLAG_VITAL)
+			Flags = NET_CHUNKFLAG_VITAL;
+		
+		if(m_aSlots[pChunk->m_ClientID].m_Connection.QueueChunk(Flags, pChunk->m_DataSize, pChunk->m_pData) == 0)
+		{
+			if(pChunk->m_Flags&NETSENDFLAG_FLUSH)
+				m_aSlots[pChunk->m_ClientID].m_Connection.Flush();
+		}
+		else
+		{
+			Drop(pChunk->m_ClientID, "error sending data");
+		}
+	}
+	return 0;
+}
+
diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp
new file mode 100644
index 00000000..3e1d8dd6
--- /dev/null
+++ b/src/engine/shared/packer.cpp
@@ -0,0 +1,155 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+
+#include "packer.h"
+#include "compression.h"
+#include "engine.h"
+#include "config.h"
+
+void CPacker::Reset()
+{
+	m_Error = 0;
+	m_pCurrent = m_aBuffer;
+	m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE;
+}
+
+void CPacker::AddInt(int i)
+{
+	if(m_Error)
+		return;
+		
+	// make sure that we have space enough
+	if(m_pEnd - m_pCurrent < 6)
+	{
+		dbg_break();
+		m_Error = 1;
+	}
+	else
+		m_pCurrent = CVariableInt::Pack(m_pCurrent, i);
+}
+
+void CPacker::AddString(const char *pStr, int Limit)
+{
+	if(m_Error)
+		return;
+	
+	//
+	if(Limit > 0)
+	{
+		while(*pStr && Limit != 0)
+		{
+			*m_pCurrent++ = *pStr++;
+			Limit--;
+			
+			if(m_pCurrent >= m_pEnd)
+			{
+				m_Error = 1;
+				break;
+			}
+		}
+		*m_pCurrent++ = 0;
+	}
+	else
+	{
+		while(*pStr)
+		{
+			*m_pCurrent++ = *pStr++;
+
+			if(m_pCurrent >= m_pEnd)
+			{
+				m_Error = 1;
+				break;
+			}
+		}
+		*m_pCurrent++ = 0;
+	}
+}
+
+void CPacker::AddRaw(const void *pData, int Size)
+{
+	if(m_Error)
+		return;
+		
+	if(m_pCurrent+Size >= m_pEnd)
+	{
+		m_Error = 1;
+		return;
+	}
+	
+	const unsigned char *pSrc = (const unsigned char *)pData;
+	while(Size)
+	{
+		*m_pCurrent++ = *pSrc++;
+		Size--;
+	}
+}
+
+
+void CUnpacker::Reset(const void *pData, int Size)
+{
+	m_Error = 0;
+	m_pStart = (const unsigned char *)pData;
+	m_pEnd = m_pStart + Size;
+	m_pCurrent = m_pStart;
+}
+
+int CUnpacker::GetInt()
+{
+	if(m_Error)
+		return 0;
+		
+	if(m_pCurrent >= m_pEnd)
+	{
+		m_Error = 1;
+		return 0;
+	}
+	
+	int i;
+	m_pCurrent = CVariableInt::Unpack(m_pCurrent, &i);
+	if(m_pCurrent > m_pEnd)
+	{
+		m_Error = 1;
+		return 0;
+	}
+	return i;
+}
+
+const char *CUnpacker::GetString()
+{
+	if(m_Error || m_pCurrent >= m_pEnd)
+		return "";
+		
+	char *pPtr = (char *)m_pCurrent;
+	while(*m_pCurrent) // skip the string
+	{
+		m_pCurrent++;
+		if(m_pCurrent == m_pEnd)
+		{
+			m_Error = 1;;
+			return "";
+		}
+	}
+	m_pCurrent++;
+	
+	// sanitize all strings
+	str_sanitize(pPtr);
+	return pPtr;
+}
+
+const unsigned char *CUnpacker::GetRaw(int Size)
+{
+	const unsigned char *pPtr = m_pCurrent;
+	if(m_Error)
+		return 0;
+	
+	// check for nasty sizes
+	if(Size < 0 || m_pCurrent+Size > m_pEnd)
+	{
+		m_Error = 1;
+		return 0;
+	}
+
+	// "unpack" the data
+	m_pCurrent += Size;
+	return pPtr;
+}
diff --git a/src/engine/shared/packer.h b/src/engine/shared/packer.h
new file mode 100644
index 00000000..7a98501a
--- /dev/null
+++ b/src/engine/shared/packer.h
@@ -0,0 +1,42 @@
+#ifndef ENGINE_SHARED_PACKER_H
+#define ENGINE_SHARED_PACKER_H
+
+
+
+class CPacker
+{
+	enum
+	{
+		PACKER_BUFFER_SIZE=1024*2
+	};
+
+	unsigned char m_aBuffer[PACKER_BUFFER_SIZE];
+	unsigned char *m_pCurrent;
+	unsigned char *m_pEnd;
+	int m_Error;
+public:
+	void Reset();
+	void AddInt(int i);
+	void AddString(const char *pStr, int Limit);
+	void AddRaw(const void *pData, int Size);
+	
+	int Size() const { return (int)(m_pCurrent-m_aBuffer); }
+	const unsigned char *Data() const { return m_aBuffer; }
+	bool Error() const { return m_Error; }
+};
+
+class CUnpacker
+{
+	const unsigned char *m_pStart;
+	const unsigned char *m_pCurrent;
+	const unsigned char *m_pEnd;
+	int m_Error;
+public:
+	void Reset(const void *pData, int Size);
+	int GetInt();
+	const char *GetString();
+	const unsigned char *GetRaw(int Size);
+	bool Error() const { return m_Error; }
+};
+
+#endif
diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h
new file mode 100644
index 00000000..d09cff5a
--- /dev/null
+++ b/src/engine/shared/protocol.h
@@ -0,0 +1,91 @@
+#ifndef ENGINE_SHARED_PROTOCOL_H
+#define ENGINE_SHARED_PROTOCOL_H
+
+#include <base/system.h>
+
+/*
+	Connection diagram - How the initilization works.
+	
+	Client -> INFO -> Server
+		Contains version info, name, and some other info.
+		
+	Client <- MAP <- Server
+		Contains current map.
+	
+	Client -> READY -> Server
+		The client has loaded the map and is ready to go,
+		but the mod needs to send it's information aswell.
+		modc_connected is called on the client and
+		mods_connected is called on the server.
+		The client should call client_entergame when the
+		mod has done it's initilization.
+		
+	Client -> ENTERGAME -> Server
+		Tells the server to start sending snapshots.
+		client_entergame and server_client_enter is called.
+*/
+
+
+enum
+{
+	NETMSG_NULL=0,
+	
+	// the first thing sent by the client
+	// contains the version info for the client
+	NETMSG_INFO=1,
+	
+	// sent by server
+	NETMSG_MAP_CHANGE,		// sent when client should switch map
+	NETMSG_MAP_DATA,		// map transfer, contains a chunk of the map file
+	NETMSG_SNAP,			// normal snapshot, multiple parts
+	NETMSG_SNAPEMPTY,		// empty snapshot
+	NETMSG_SNAPSINGLE,		// ?
+	NETMSG_SNAPSMALL,		//
+	NETMSG_INPUTTIMING,		// reports how off the input was
+	NETMSG_RCON_AUTH_STATUS,// result of the authentication
+	NETMSG_RCON_LINE,		// line that should be printed to the remote console
+
+	NETMSG_AUTH_CHALLANGE,	//
+	NETMSG_AUTH_RESULT,		//
+	
+	// sent by client
+	NETMSG_READY,			//
+	NETMSG_ENTERGAME,
+	NETMSG_INPUT,			// contains the inputdata from the client
+	NETMSG_RCON_CMD,		// 
+	NETMSG_RCON_AUTH,		//
+	NETMSG_REQUEST_MAP_DATA,//
+
+	NETMSG_AUTH_START,		//
+	NETMSG_AUTH_RESPONSE,	//
+	
+	// sent by both
+	NETMSG_PING,
+	NETMSG_PING_REPLY,
+	NETMSG_ERROR
+};
+
+// this should be revised
+enum
+{
+	SERVER_TICK_SPEED=50,
+	SERVER_FLAG_PASSWORD = 0x1,
+
+	MAX_CLIENTS=16,
+
+	MAX_INPUT_SIZE=128,
+	MAX_SNAPSHOT_PACKSIZE=900,
+
+	MAX_CLANNAME_LENGTH=32,
+	MAX_NAME_LENGTH=24,
+	
+
+	// message packing
+	MSGFLAG_VITAL=1,
+	MSGFLAG_FLUSH=2,
+	MSGFLAG_NORECORD=4,
+	MSGFLAG_RECORD=8,
+	MSGFLAG_NOSEND=16
+};
+
+#endif
diff --git a/src/engine/shared/ringbuffer.cpp b/src/engine/shared/ringbuffer.cpp
new file mode 100644
index 00000000..45a845ee
--- /dev/null
+++ b/src/engine/shared/ringbuffer.cpp
@@ -0,0 +1,192 @@
+#include <base/system.h>
+
+#include "ringbuffer.h"
+	
+CRingBufferBase::CItem *CRingBufferBase::NextBlock(CItem *pItem)
+{
+	if(pItem->m_pNext)
+		return pItem->m_pNext;
+	return m_pFirst;
+}
+
+CRingBufferBase::CItem *CRingBufferBase::PrevBlock(CItem *pItem)
+{
+	if(pItem->m_pPrev)
+		return pItem->m_pPrev;
+	return m_pLast;
+}
+
+CRingBufferBase::CItem *CRingBufferBase::MergeBack(CItem *pItem)
+{
+	// make sure that this block and previous block is free
+	if(!pItem->m_Free || !pItem->m_pPrev || !pItem->m_pPrev->m_Free)
+		return pItem;
+
+	// merge the blocks
+	pItem->m_pPrev->m_Size += pItem->m_Size;
+	pItem->m_pPrev->m_pNext = pItem->m_pNext;
+	
+	// fixup pointers
+	if(pItem->m_pNext)
+		pItem->m_pNext->m_pPrev = pItem->m_pPrev;
+	else
+		m_pLast = pItem->m_pPrev;
+		
+	if(pItem == m_pProduce)
+		m_pProduce = pItem->m_pPrev;
+	
+	if(pItem == m_pConsume)
+		m_pConsume = pItem->m_pPrev;
+	
+	// return the current block
+	return pItem->m_pPrev;
+}
+
+void CRingBufferBase::Init(void *pMemory, int Size, int Flags)
+{
+	mem_zero(pMemory, Size);
+	m_Size = (Size)/sizeof(CItem)*sizeof(CItem);
+	m_pFirst = (CItem *)pMemory;
+	m_pFirst->m_Free = 1;
+	m_pFirst->m_Size = m_Size;
+	m_pLast = m_pFirst;
+	m_pProduce = m_pFirst;
+	m_pConsume = m_pFirst;
+	m_Flags = Flags;		
+	
+}
+
+void *CRingBufferBase::Allocate(int Size)
+{
+	int WantedSize = (Size+sizeof(CItem)+sizeof(CItem)-1)/sizeof(CItem)*sizeof(CItem);
+	CItem *pBlock = 0;
+
+	// check if we even can fit this block
+	if(WantedSize > m_Size)
+		return 0;
+
+	while(1)	
+	{
+		// check for space
+		if(m_pProduce->m_Free)
+		{
+			if(m_pProduce->m_Size >= WantedSize)
+				pBlock = m_pProduce;
+			else
+			{
+				// wrap around to try to find a block
+				if(m_pFirst->m_Free && m_pFirst->m_Size >= WantedSize)
+					pBlock = m_pFirst;
+			}
+		}
+		
+		if(pBlock)
+			break;
+		else
+		{
+			// we have no block, check our policy and see what todo
+			if(m_Flags&FLAG_RECYCLE)
+			{
+				if(!PopFirst())
+					return 0;
+			}
+			else
+				return 0;
+		}
+	}
+	
+	// okey, we have our block
+	
+	// split the block if needed
+	if(pBlock->m_Size > WantedSize+sizeof(CItem))
+	{
+		CItem *pNewItem = (CItem *)((char *)pBlock + WantedSize);
+		pNewItem->m_pPrev = pBlock;
+		pNewItem->m_pNext = pBlock->m_pNext;
+		if(pNewItem->m_pNext)
+			pNewItem->m_pNext->m_pPrev = pNewItem;
+		pBlock->m_pNext = pNewItem;
+		
+		pNewItem->m_Free = 1;
+		pNewItem->m_Size = pBlock->m_Size - WantedSize;
+		pBlock->m_Size = WantedSize;
+		
+		if(!pNewItem->m_pNext)
+			m_pLast = pNewItem;
+	}
+	
+	
+	// set next block
+	m_pProduce = NextBlock(pBlock);
+	
+	// set as used and return the item pointer
+	pBlock->m_Free = 0;
+	return (void *)(pBlock+1);
+}
+
+int CRingBufferBase::PopFirst()
+{
+	if(m_pConsume->m_Free)
+		return 0;
+	
+	// set the free flag
+	m_pConsume->m_Free = 1;
+	
+	// previous block is also free, merge them
+	m_pConsume = MergeBack(m_pConsume);
+	
+	// advance the consume pointer
+	m_pConsume = NextBlock(m_pConsume);
+	while(m_pConsume->m_Free && m_pConsume != m_pProduce)
+	{
+		m_pConsume = MergeBack(m_pConsume);
+		m_pConsume = NextBlock(m_pConsume);
+	}
+		
+	// in the case that we have catched up with the produce pointer
+	// we might stand on a free block so merge em
+	MergeBack(m_pConsume);
+	return 1;
+}
+
+
+void *CRingBufferBase::Prev(void *pCurrent)
+{
+	CItem *pItem = ((CItem *)pCurrent) - 1;
+	
+	while(1)
+	{
+		pItem = PrevBlock(pItem);
+		if(pItem == m_pProduce)
+			return 0;
+		if(!pItem->m_Free)
+			return pItem+1;
+	}
+}
+
+void *CRingBufferBase::Next(void *pCurrent)
+{
+	CItem *pItem = ((CItem *)pCurrent) - 1;
+	
+	while(1)
+	{
+		pItem = NextBlock(pItem);
+		if(pItem == m_pProduce)
+			return 0;
+		if(!pItem->m_Free)
+			return pItem+1;
+	}
+}
+
+void *CRingBufferBase::First()
+{
+	if(m_pConsume->m_Free)
+		return 0;
+	return (void *)(m_pConsume+1);
+}
+
+void *CRingBufferBase::Last()
+{
+	return Prev(m_pProduce+1);
+}
+
diff --git a/src/engine/shared/ringbuffer.h b/src/engine/shared/ringbuffer.h
new file mode 100644
index 00000000..aa02b8d9
--- /dev/null
+++ b/src/engine/shared/ringbuffer.h
@@ -0,0 +1,66 @@
+#ifndef ENGINE_SHARED_RINGBUFFER_H
+#define ENGINE_SHARED_RINGBUFFER_H
+
+typedef struct RINGBUFFER RINGBUFFER;
+
+class CRingBufferBase
+{
+	class CItem
+	{
+	public:
+		CItem *m_pPrev;
+		CItem *m_pNext;
+		int m_Free;
+		int m_Size;
+	};
+	
+	CItem *m_pProduce;
+	CItem *m_pConsume;
+	
+	CItem *m_pFirst;
+	CItem *m_pLast;
+	
+	void *m_pMemory;
+	int m_Size;
+	int m_Flags;
+	
+	CItem *NextBlock(CItem *pItem);
+	CItem *PrevBlock(CItem *pItem);
+	CItem *MergeBack(CItem *pItem);
+protected:
+	void *Allocate(int Size);
+	
+	void *Prev(void *pCurrent);
+	void *Next(void *pCurrent);
+	void *First();
+	void *Last();
+	
+	void Init(void *pMemory, int Size, int Flags);
+	int PopFirst();
+public:
+	enum
+	{
+		// Will start to destroy items to try to fit the next one
+		FLAG_RECYCLE=1
+	};
+};
+
+template<typename T, int TSIZE, int TFLAGS=0>
+class TStaticRingBuffer : public CRingBufferBase
+{
+	unsigned char m_aBuffer[TSIZE];
+public:
+	TStaticRingBuffer() { Init(); }
+	
+	void Init() { CRingBufferBase::Init(m_aBuffer, TSIZE, TFLAGS); }
+	
+	T *Allocate(int Size) { return (T*)CRingBufferBase::Allocate(Size); }
+	int PopFirst() { return CRingBufferBase::PopFirst(); }
+
+	T *Prev(T *pCurrent) { return (T*)CRingBufferBase::Prev(pCurrent); }
+	T *Next(T *pCurrent) { return (T*)CRingBufferBase::Next(pCurrent); }
+	T *First() { return (T*)CRingBufferBase::First(); }
+	T *Last() { return (T*)CRingBufferBase::Last(); }
+};
+
+#endif
diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp
new file mode 100644
index 00000000..d566d3a3
--- /dev/null
+++ b/src/engine/shared/snapshot.cpp
@@ -0,0 +1,537 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include "snapshot.h"
+#include "engine.h"
+#include "compression.h"
+
+// CSnapshot
+
+CSnapshotItem *CSnapshot::GetItem(int Index)
+{
+	return (CSnapshotItem *)(DataStart() + Offsets()[Index]);
+}
+
+int CSnapshot::GetItemSize(int Index)
+{
+    if(Index == m_NumItems-1)
+        return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem);
+    return (Offsets()[Index+1] - Offsets()[Index]) - sizeof(CSnapshotItem);
+}
+
+int CSnapshot::GetItemIndex(int Key)
+{
+    // TODO: OPT: this should not be a linear search. very bad
+    for(int i = 0; i < m_NumItems; i++)
+    {
+        if(GetItem(i)->Key() == Key)
+            return i;
+    }
+    return -1;
+}
+
+int CSnapshot::Crc()
+{
+	int Crc = 0;
+	
+	for(int i = 0; i < m_NumItems; i++)
+	{
+		CSnapshotItem *pItem = GetItem(i);
+		int Size = GetItemSize(i);
+		
+		for(int b = 0; b < Size/4; b++)
+			Crc += pItem->Data()[b];
+	}
+	return Crc;
+}
+
+void CSnapshot::DebugDump()
+{
+	dbg_msg("snapshot", "data_size=%d num_items=%d", m_DataSize, m_NumItems);
+	for(int i = 0; i < m_NumItems; i++)
+	{
+		CSnapshotItem *pItem = GetItem(i);
+		int Size = GetItemSize(i);
+		dbg_msg("snapshot", "\ttype=%d id=%d", pItem->Type(), pItem->ID());
+		for(int b = 0; b < Size/4; b++)
+			dbg_msg("snapshot", "\t\t%3d %12d\t%08x", b, pItem->Data()[b], pItem->Data()[b]);
+	}
+}
+
+
+// CSnapshotDelta
+
+struct CItemList
+{
+	int m_Num;
+	int m_aKeys[64];
+	int m_aIndex[64];
+};
+
+enum
+{
+	HASHLIST_SIZE = 256,
+};
+
+static void GenerateHash(CItemList *pHashlist, CSnapshot *pSnapshot)
+{
+	for(int i = 0; i < HASHLIST_SIZE; i++)
+		pHashlist[i].m_Num = 0;
+		
+	for(int i = 0; i < pSnapshot->NumItems(); i++)
+	{
+		int Key = pSnapshot->GetItem(i)->Key();
+		int HashID = ((Key>>8)&0xf0) | (Key&0xf);
+		if(pHashlist[HashID].m_Num != 64)
+		{
+			pHashlist[HashID].m_aIndex[pHashlist[HashID].m_Num] = i;
+			pHashlist[HashID].m_aKeys[pHashlist[HashID].m_Num] = Key;
+			pHashlist[HashID].m_Num++;
+		}
+	}
+}
+
+static int GetItemIndexHashed(int Key, const CItemList *pHashlist)
+{
+		int HashID = ((Key>>8)&0xf0) | (Key&0xf);
+		for(int i = 0; i < pHashlist[HashID].m_Num; i++)
+		{
+			if(pHashlist[HashID].m_aKeys[i] == Key)
+				return pHashlist[HashID].m_aIndex[i];
+	}
+	
+	return -1;
+}
+
+static int DiffItem(int *pPast, int *pCurrent, int *pOut, int Size)
+{
+	int Needed = 0;
+	while(Size)
+	{
+		*pOut = *pCurrent-*pPast;
+		Needed |= *pOut;
+		pOut++;
+		pPast++;
+		pCurrent++;
+		Size--;
+	}
+	
+	return Needed;
+}
+
+void CSnapshotDelta::UndiffItem(int *pPast, int *pDiff, int *pOut, int Size)
+{
+	while(Size)
+	{
+		*pOut = *pPast+*pDiff;
+		
+		if(*pDiff == 0)
+			m_aSnapshotDataRate[m_SnapshotCurrent] += 1;
+		else
+		{
+			unsigned char aBuf[16];
+			unsigned char *pEnd = CVariableInt::Pack(aBuf,  *pDiff);
+			m_aSnapshotDataRate[m_SnapshotCurrent] += (int)(pEnd - (unsigned char*)aBuf) * 8;
+		}
+		
+		pOut++;
+		pPast++;
+		pDiff++;
+		Size--;
+	}
+}
+
+CSnapshotDelta::CSnapshotDelta()
+{
+	mem_zero(m_aItemSizes, sizeof(m_aItemSizes));
+	mem_zero(m_aSnapshotDataRate, sizeof(m_aSnapshotDataRate));
+	mem_zero(m_aSnapshotDataUpdates, sizeof(m_aSnapshotDataUpdates));
+	m_SnapshotCurrent = 0;
+	mem_zero(&m_Empty, sizeof(m_Empty));
+}
+
+void CSnapshotDelta::SetStaticsize(int ItemType, int Size)
+{
+	m_aItemSizes[ItemType] = Size;
+}
+
+CSnapshotDelta::CData *CSnapshotDelta::EmptyDelta()
+{
+	return &m_Empty;
+}
+
+// TODO: OPT: this should be made much faster
+int CSnapshotDelta::CreateDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pDstData)
+{
+	CData *pDelta = (CData *)pDstData;
+	int *pData = (int *)pDelta->m_pData;
+	int i, ItemSize, PastIndex;
+	CSnapshotItem *pFromItem;
+	CSnapshotItem *pCurItem;
+	CSnapshotItem *pPastItem;
+	int Count = 0;
+	int SizeCount = 0;
+	
+	pDelta->m_NumDeletedItems = 0;
+	pDelta->m_NumUpdateItems = 0;
+	pDelta->m_NumTempItems = 0;
+	
+	CItemList Hashlist[HASHLIST_SIZE];
+	GenerateHash(Hashlist, pTo);
+
+	// pack deleted stuff
+	for(i = 0; i < pFrom->NumItems(); i++)
+	{
+		pFromItem = pFrom->GetItem(i);
+		if(GetItemIndexHashed(pFromItem->Key(), Hashlist) == -1)
+		{
+			// deleted
+			pDelta->m_NumDeletedItems++;
+			*pData = pFromItem->Key();
+			pData++;
+		}
+	}
+	
+	GenerateHash(Hashlist, pFrom);
+	int aPastIndecies[1024];
+
+	// fetch previous indices
+	// we do this as a separate pass because it helps the cache
+	for(i = 0; i < pTo->NumItems(); i++)
+	{
+		pCurItem = pTo->GetItem(i);  // O(1) .. O(n)
+		aPastIndecies[i] = GetItemIndexHashed(pCurItem->Key(), Hashlist); // O(n) .. O(n^n)
+	}
+		
+	for(i = 0; i < pTo->NumItems(); i++)
+	{
+		// do delta
+		ItemSize = pTo->GetItemSize(i); // O(1) .. O(n)
+		pCurItem = pTo->GetItem(i);  // O(1) .. O(n)
+		PastIndex = aPastIndecies[i];
+		
+		if(PastIndex != -1)
+		{
+			int *pItemDataDst = pData+3;
+	
+			pPastItem = pFrom->GetItem(PastIndex);
+			
+			if(m_aItemSizes[pCurItem->Type()])
+				pItemDataDst = pData+2;
+			
+			if(DiffItem((int*)pPastItem->Data(), (int*)pCurItem->Data(), pItemDataDst, ItemSize/4))
+			{
+				
+				*pData++ = pCurItem->Type();
+				*pData++ = pCurItem->ID();
+				if(!m_aItemSizes[pCurItem->Type()])
+					*pData++ = ItemSize/4;
+				pData += ItemSize/4;
+				pDelta->m_NumUpdateItems++;
+			}
+		}
+		else
+		{
+			*pData++ = pCurItem->Type();
+			*pData++ = pCurItem->ID();
+			if(!m_aItemSizes[pCurItem->Type()])
+				*pData++ = ItemSize/4;
+			
+			mem_copy(pData, pCurItem->Data(), ItemSize);
+			SizeCount += ItemSize;
+			pData += ItemSize/4;
+			pDelta->m_NumUpdateItems++;
+			Count++;
+		}
+	}
+	
+	if(0)
+	{
+		dbg_msg("snapshot", "%d %d %d",
+			pDelta->m_NumDeletedItems,
+			pDelta->m_NumUpdateItems,
+			pDelta->m_NumTempItems);
+	}
+
+	/*
+	// TODO: pack temp stuff
+	
+	// finish
+	//mem_copy(pDelta->offsets, deleted, pDelta->num_deleted_items*sizeof(int));
+	//mem_copy(&(pDelta->offsets[pDelta->num_deleted_items]), update, pDelta->num_update_items*sizeof(int));
+	//mem_copy(&(pDelta->offsets[pDelta->num_deleted_items+pDelta->num_update_items]), temp, pDelta->num_temp_items*sizeof(int));
+	//mem_copy(pDelta->data_start(), data, data_size);
+	//pDelta->data_size = data_size;
+	* */
+	
+	if(!pDelta->m_NumDeletedItems && !pDelta->m_NumUpdateItems && !pDelta->m_NumTempItems)
+		return 0;
+	
+	return (int)((char*)pData-(char*)pDstData);
+}
+
+static int RangeCheck(void *pEnd, void *pPtr, int Size)
+{
+	if((const char *)pPtr + Size > (const char *)pEnd)
+		return -1;
+	return 0;
+}
+
+int CSnapshotDelta::UnpackDelta(CSnapshot *pFrom, CSnapshot *pTo, void *pSrcData, int DataSize)
+{
+	CSnapshotBuilder Builder;
+	CData *pDelta = (CData *)pSrcData;
+	int *pData = (int *)pDelta->m_pData;
+	int *pEnd = (int *)(((char *)pSrcData + DataSize));
+	
+	CSnapshotItem *pFromItem;
+	int Keep, ItemSize;
+	int *pDeleted;
+	int Id, Type, Key;
+	int FromIndex;
+	int *pNewData;
+			
+	Builder.Init();
+	
+	// unpack deleted stuff
+	pDeleted = pData;
+	pData += pDelta->m_NumDeletedItems;
+	if(pData > pEnd)
+		return -1;
+
+	// copy all non deleted stuff
+	for(int i = 0; i < pFrom->NumItems(); i++)
+	{
+		// dbg_assert(0, "fail!");
+		pFromItem = pFrom->GetItem(i);
+		ItemSize = pFrom->GetItemSize(i); 
+		Keep = 1;
+		for(int d = 0; d < pDelta->m_NumDeletedItems; d++)
+		{
+			if(pDeleted[d] == pFromItem->Key())
+			{
+				Keep = 0;
+				break;
+			}
+		}
+		
+		if(Keep)
+		{
+			// keep it
+			mem_copy(
+				Builder.NewItem(pFromItem->Type(), pFromItem->ID(), ItemSize),
+				pFromItem->Data(), ItemSize);
+		}
+	}
+		
+	// unpack updated stuff
+	for(int i = 0; i < pDelta->m_NumUpdateItems; i++)
+	{
+		if(pData+2 > pEnd)
+			return -1;
+		
+		Type = *pData++;
+		Id = *pData++;
+		if(m_aItemSizes[Type])
+			ItemSize = m_aItemSizes[Type];
+		else
+		{
+			if(pData+1 > pEnd)
+				return -2;
+			ItemSize = (*pData++) * 4;
+		}
+		m_SnapshotCurrent = Type;
+		
+		if(RangeCheck(pEnd, pData, ItemSize) || ItemSize < 0) return -3;
+		
+		Key = (Type<<16)|Id;
+		
+		// create the item if needed
+		pNewData = Builder.GetItemData(Key);
+		if(!pNewData)
+			pNewData = (int *)Builder.NewItem(Key>>16, Key&0xffff, ItemSize);
+
+		//if(range_check(pEnd, pNewData, ItemSize)) return -4;
+			
+		FromIndex = pFrom->GetItemIndex(Key);
+		if(FromIndex != -1)
+		{
+			// we got an update so we need pTo apply the diff
+			UndiffItem((int *)pFrom->GetItem(FromIndex)->Data(), pData, pNewData, ItemSize/4);
+			m_aSnapshotDataUpdates[m_SnapshotCurrent]++;
+		}
+		else // no previous, just copy the pData
+		{
+			mem_copy(pNewData, pData, ItemSize);
+			m_aSnapshotDataRate[m_SnapshotCurrent] += ItemSize*8;
+			m_aSnapshotDataUpdates[m_SnapshotCurrent]++;
+		}
+			
+		pData += ItemSize/4;
+	}
+	
+	// finish up
+	return Builder.Finish(pTo);
+}
+
+
+// CSnapshotStorage
+
+void CSnapshotStorage::Init()
+{
+	m_pFirst = 0;
+	m_pLast = 0;
+}
+
+void CSnapshotStorage::PurgeAll()
+{
+	CHolder *pHolder = m_pFirst;
+	CHolder *pNext;
+
+	while(pHolder)
+	{
+		pNext = pHolder->m_pNext;
+		mem_free(pHolder);
+		pHolder = pNext;
+	}
+
+	// no more snapshots in storage
+	m_pFirst = 0;
+	m_pLast = 0;
+}
+
+void CSnapshotStorage::PurgeUntil(int Tick)
+{
+	CHolder *pHolder = m_pFirst;
+	CHolder *pNext;
+	
+	while(pHolder)
+	{
+		pNext = pHolder->m_pNext;
+		if(pHolder->m_Tick >= Tick)
+			return; // no more to remove
+		mem_free(pHolder);
+		
+		// did we come to the end of the list?
+        if (!pNext)
+            break;
+
+		m_pFirst = pNext;
+		pNext->m_pPrev = 0x0;
+		
+		pHolder = pNext;
+	}
+	
+	// no more snapshots in storage
+	m_pFirst = 0;
+	m_pLast = 0;
+}
+
+void CSnapshotStorage::Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt)
+{
+	// allocate memory for holder + snapshot_data
+	int TotalSize = sizeof(CHolder)+DataSize;
+	
+	if(CreateAlt)
+		TotalSize += DataSize;
+	
+	CHolder *pHolder = (CHolder *)mem_alloc(TotalSize, 1);
+	
+	// set data
+	pHolder->m_Tick = Tick;
+	pHolder->m_Tagtime = Tagtime;
+	pHolder->m_SnapSize = DataSize;
+	pHolder->m_pSnap = (CSnapshot*)(pHolder+1);
+	mem_copy(pHolder->m_pSnap, pData, DataSize);
+
+	if(CreateAlt) // create alternative if wanted
+	{
+		pHolder->m_pAltSnap = (CSnapshot*)(((char *)pHolder->m_pSnap) + DataSize);
+		mem_copy(pHolder->m_pAltSnap, pData, DataSize);
+	}
+	else
+		pHolder->m_pAltSnap = 0;
+		
+	
+	// link
+	pHolder->m_pNext = 0;
+	pHolder->m_pPrev = m_pLast;
+	if(m_pLast)
+		m_pLast->m_pNext = pHolder;
+	else
+		m_pFirst = pHolder;
+	m_pLast = pHolder;
+}
+
+int CSnapshotStorage::Get(int Tick, int64 *pTagtime, CSnapshot **ppData, CSnapshot **ppAltData)
+{
+	CHolder *pHolder = m_pFirst;
+	
+	while(pHolder)
+	{
+		if(pHolder->m_Tick == Tick)
+		{
+			if(pTagtime)
+				*pTagtime = pHolder->m_Tagtime;
+			if(ppData)
+				*ppData = pHolder->m_pSnap;
+			if(ppAltData)
+				*ppData = pHolder->m_pAltSnap;
+			return pHolder->m_SnapSize;
+		}
+		
+		pHolder = pHolder->m_pNext;
+	}
+	
+	return -1;
+}
+
+// CSnapshotBuilder
+
+void CSnapshotBuilder::Init()
+{
+	m_DataSize = 0;
+	m_NumItems = 0;
+}
+
+CSnapshotItem *CSnapshotBuilder::GetItem(int Index) 
+{
+	return (CSnapshotItem *)&(m_aData[m_aOffsets[Index]]);
+}
+
+int *CSnapshotBuilder::GetItemData(int Key)
+{
+	int i;
+	for(i = 0; i < m_NumItems; i++)
+	{
+		if(GetItem(i)->Key() == Key)
+			return (int *)GetItem(i)->Data();
+	}
+	return 0;
+}
+
+int CSnapshotBuilder::Finish(void *SpnapData)
+{
+	// flattern and make the snapshot
+	CSnapshot *pSnap = (CSnapshot *)SpnapData;
+	int OffsetSize = sizeof(int)*m_NumItems;
+	pSnap->m_DataSize = m_DataSize;
+	pSnap->m_NumItems = m_NumItems;
+	mem_copy(pSnap->Offsets(), m_aOffsets, OffsetSize);
+	mem_copy(pSnap->DataStart(), m_aData, m_DataSize);
+	return sizeof(CSnapshot) + OffsetSize + m_DataSize;
+}
+
+void *CSnapshotBuilder::NewItem(int Type, int ID, int Size)
+{
+	CSnapshotItem *pObj = (CSnapshotItem *)(m_aData + m_DataSize);
+
+	mem_zero(pObj, sizeof(CSnapshotItem) + Size);
+	pObj->m_TypeAndID = (Type<<16)|ID;
+	m_aOffsets[m_NumItems] = m_DataSize;
+	m_DataSize += sizeof(CSnapshotItem) + Size;
+	m_NumItems++;
+	
+	dbg_assert(m_DataSize < CSnapshot::MAX_SIZE, "too much data");
+	dbg_assert(m_NumItems < MAX_ITEMS, "too many items");
+
+	return pObj->Data();
+}
diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h
new file mode 100644
index 00000000..ec27d004
--- /dev/null
+++ b/src/engine/shared/snapshot.h
@@ -0,0 +1,136 @@
+#ifndef ENGINE_SHARED_SNAPSHOT_H
+#define ENGINE_SHARED_SNAPSHOT_H
+
+#include <base/system.h>
+
+// CSnapshot
+
+class CSnapshotItem
+{
+public:
+	int m_TypeAndID;
+	
+	int *Data() { return (int *)(this+1); }
+	int Type() { return m_TypeAndID>>16; }
+	int ID() { return m_TypeAndID&0xffff; }
+	int Key() { return m_TypeAndID; }
+};
+
+
+class CSnapshot
+{
+	friend class CSnapshotBuilder;
+	int m_DataSize;
+	int m_NumItems;
+
+	int *Offsets() const { return (int *)(this+1); }
+	char *DataStart() const { return (char*)(Offsets()+m_NumItems); }
+
+public:
+	enum
+	{
+		MAX_SIZE=64*1024
+	};
+
+	void Clear() { m_DataSize = 0; m_NumItems = 0; }
+	int NumItems() const { return m_NumItems; }
+	CSnapshotItem *GetItem(int Index);
+	int GetItemSize(int Index);
+	int GetItemIndex(int Key);
+
+	int Crc();
+	void DebugDump();
+};
+
+
+// CSnapshotDelta
+
+class CSnapshotDelta
+{
+public:
+	class CData
+	{
+	public:
+		int m_NumDeletedItems;
+		int m_NumUpdateItems;
+		int m_NumTempItems; // needed?
+		int m_pData[1];
+	};
+
+private:
+	// TODO: strange arbitrary number
+	short m_aItemSizes[64];
+	int m_aSnapshotDataRate[0xffff];
+	int m_aSnapshotDataUpdates[0xffff];
+	int m_SnapshotCurrent;
+	CData m_Empty;
+
+	void UndiffItem(int *pPast, int *pDiff, int *pOut, int Size);
+
+public:
+	CSnapshotDelta();
+	int GetDataRate(int Index) { return m_aSnapshotDataRate[Index]; }
+	int GetDataUpdates(int Index) { return m_aSnapshotDataUpdates[Index]; }
+	void SetStaticsize(int ItemType, int Size);
+	CData *EmptyDelta();
+	int CreateDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData);
+	int UnpackDelta(class CSnapshot *pFrom, class CSnapshot *pTo, void *pData, int DataSize);
+};
+
+
+// CSnapshotStorage
+
+class CSnapshotStorage
+{
+public:
+	class CHolder
+	{
+	public:
+		CHolder *m_pPrev;
+		CHolder *m_pNext;
+		
+		int64 m_Tagtime;
+		int m_Tick;
+		
+		int m_SnapSize;
+		CSnapshot *m_pSnap;
+		CSnapshot *m_pAltSnap;
+	};
+	 
+
+	CHolder *m_pFirst;
+	CHolder *m_pLast;
+
+	void Init();
+	void PurgeAll();
+	void PurgeUntil(int Tick);
+	void Add(int Tick, int64 Tagtime, int DataSize, void *pData, int CreateAlt);
+	int Get(int Tick, int64 *Tagtime, CSnapshot **pData, CSnapshot **ppAltData);
+};
+
+class CSnapshotBuilder
+{
+	enum
+	{
+		MAX_ITEMS = 1024*2
+	};
+
+	char m_aData[CSnapshot::MAX_SIZE];
+	int m_DataSize;
+
+	int m_aOffsets[MAX_ITEMS];
+	int m_NumItems;
+
+public:
+	void Init();
+	
+	void *NewItem(int Type, int ID, int Size);
+	
+	CSnapshotItem *GetItem(int Index);
+	int *GetItemData(int Key);
+	
+	int Finish(void *Snapdata);
+};
+
+
+#endif // ENGINE_SNAPSHOT_H
diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp
new file mode 100644
index 00000000..491795ad
--- /dev/null
+++ b/src/engine/shared/storage.cpp
@@ -0,0 +1,196 @@
+// copyright (c) 2007 magnus auvinen, see licence.txt for more info
+#include <base/system.h>
+#include <engine/storage.h>
+#include "engine.h"
+
+// compiled-in data-dir path
+#define DATA_DIR "data"
+
+class CStorage : public IStorage
+{
+public:
+	char m_aApplicationSavePath[512];
+	char m_aDatadir[512];
+	
+	CStorage()
+	{
+		m_aApplicationSavePath[0] = 0;
+		m_aDatadir[0] = 0;
+	}
+	
+	int Init(const char *pApplicationName, const char *pArgv0)
+	{
+		char aPath[1024] = {0};
+		fs_storage_path(pApplicationName, m_aApplicationSavePath, sizeof(m_aApplicationSavePath));
+		if(fs_makedir(m_aApplicationSavePath) == 0)
+		{		
+			str_format(aPath, sizeof(aPath), "%s/screenshots", m_aApplicationSavePath);
+			fs_makedir(aPath);
+
+			str_format(aPath, sizeof(aPath), "%s/maps", m_aApplicationSavePath);
+			fs_makedir(aPath);
+
+			str_format(aPath, sizeof(aPath), "%s/downloadedmaps", m_aApplicationSavePath);
+			fs_makedir(aPath);
+
+			str_format(aPath, sizeof(aPath), "%s/demos", m_aApplicationSavePath);
+			fs_makedir(aPath);
+		}
+		
+		return FindDatadir(pArgv0);
+	}
+		
+	int FindDatadir(const char *pArgv0)
+	{
+		// 1) use provided data-dir override
+		if(m_aDatadir[0])
+		{
+			if(fs_is_dir(m_aDatadir))
+				return 0;
+			else
+			{
+				dbg_msg("engine/datadir", "specified data-dir '%s' does not exist", m_aDatadir);
+				return -1;
+			}
+		}
+		
+		// 2) use data-dir in PWD if present
+		if(fs_is_dir("data/mapres"))
+		{
+			str_copy(m_aDatadir, "data", sizeof(m_aDatadir));
+			return 0;
+		}
+		
+		// 3) use compiled-in data-dir if present
+		if (fs_is_dir(DATA_DIR "/mapres"))
+		{
+			str_copy(m_aDatadir, DATA_DIR, sizeof(m_aDatadir));
+			return 0;
+		}
+		
+		// 4) check for usable path in argv[0]
+		{
+			unsigned int Pos = ~0U;
+			for(unsigned i = 0; pArgv0[i]; i++)
+				if(pArgv0[i] == '/')
+					Pos = i;
+			
+			if (Pos < sizeof(m_aDatadir))
+			{
+				char aBaseDir[sizeof(m_aDatadir)];
+				str_copy(aBaseDir, pArgv0, Pos);
+				aBaseDir[Pos] = '\0';
+				str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", aBaseDir);
+				
+				if (fs_is_dir(m_aDatadir))
+					return 0;
+			}
+		}
+		
+	#if defined(CONF_FAMILY_UNIX)
+		// 5) check for all default locations
+		{
+			const char *aDirs[] = {
+				"/usr/share/teeworlds/data",
+				"/usr/share/games/teeworlds/data",
+				"/usr/local/share/teeworlds/data",
+				"/usr/local/share/games/teeworlds/data",
+				"/opt/teeworlds/data"
+			};
+			const int DirsCount = sizeof(aDirs) / sizeof(aDirs[0]);
+			
+			int i;
+			for (i = 0; i < DirsCount; i++)
+			{
+				if (fs_is_dir(aDirs[i]))
+				{
+					str_copy(m_aDatadir, aDirs[i], sizeof(m_aDatadir));
+					return 0;
+				}
+			}
+		}
+	#endif
+		
+		// no data-dir found
+		dbg_msg("engine/datadir", "warning no data directory found");
+		return -1;
+	}
+
+	virtual void ListDirectory(int Types, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser)
+	{
+		char aBuffer[1024];
+		
+		// list current directory
+		if(Types&TYPE_CURRENT)
+		{
+			fs_listdir(pPath, pfnCallback, pUser);
+		}
+		
+		// list users directory
+		if(Types&TYPE_SAVE)
+		{
+			str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aApplicationSavePath, pPath);
+			fs_listdir(aBuffer, pfnCallback, pUser);
+		}
+		
+		// list datadir directory
+		if(Types&TYPE_DATA)
+		{
+			str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aDatadir, pPath);
+			fs_listdir(aBuffer, pfnCallback, pUser);
+		}		
+	}
+	
+	virtual IOHANDLE OpenFile(const char *pFilename, int Flags, char *pBuffer = 0, int BufferSize = 0)
+	{
+		char aBuffer[1024];
+		if(!pBuffer)
+		{
+			pBuffer = aBuffer;
+			BufferSize = sizeof(aBuffer);
+		}
+		
+		if(Flags&IOFLAG_WRITE)
+		{
+			str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename);
+			return io_open(pBuffer, Flags);
+		}
+		else
+		{
+			IOHANDLE Handle = 0;
+			
+			// check current directory
+			Handle = io_open(pFilename, Flags);
+			if(Handle)
+				return Handle;
+				
+			// check user directory
+			str_format(pBuffer, BufferSize, "%s/%s", m_aApplicationSavePath, pFilename);
+			Handle = io_open(pBuffer, Flags);
+			if(Handle)
+				return Handle;
+				
+			// check normal data directory
+			str_format(pBuffer, BufferSize, "%s/%s", m_aDatadir, pFilename);
+			Handle = io_open(pBuffer, Flags);
+			if(Handle)
+				return Handle;
+		}
+		
+		pBuffer[0] = 0;
+		return 0;		
+	}
+
+	static IStorage *Create(const char *pApplicationName, const char *pArgv0)
+	{
+		CStorage *p = new CStorage();
+		if(p->Init(pApplicationName, pArgv0))
+		{
+			delete p;
+			p = 0;
+		}
+		return p;
+	}
+};
+
+IStorage *CreateStorage(const char *pApplicationName, const char *pArgv0) { return CStorage::Create(pApplicationName, pArgv0); }