about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.cpp599
-rw-r--r--src/datafile.cpp444
-rw-r--r--src/datafile.h20
-rw-r--r--src/editor.cpp1229
-rw-r--r--src/game/game.h231
-rw-r--r--src/game/game_client.cpp1992
-rw-r--r--src/game/game_server.cpp2122
-rw-r--r--src/game/mapres.h7
-rw-r--r--src/game/mapres_col.cpp44
-rw-r--r--src/game/mapres_col.h10
-rw-r--r--src/game/mapres_image.cpp41
-rw-r--r--src/game/mapres_image.h18
-rw-r--r--src/game/mapres_tilemap.cpp54
-rw-r--r--src/game/mapres_tilemap.h19
-rw-r--r--src/gfx.cpp581
-rw-r--r--src/interface.h711
-rw-r--r--src/lzw.cpp213
-rw-r--r--src/lzw.h2
-rw-r--r--src/main.cpp25
-rw-r--r--src/map.cpp48
-rw-r--r--src/menu.cpp597
-rw-r--r--src/packet.h297
-rw-r--r--src/server.cpp661
-rw-r--r--src/snapshot.h19
-rw-r--r--src/snd.cpp519
-rw-r--r--src/ui.cpp115
-rw-r--r--src/ui.h33
-rw-r--r--src/wavpack/arm.S461
-rw-r--r--src/wavpack/arml.S491
-rw-r--r--src/wavpack/bits.c140
-rw-r--r--src/wavpack/coldfire.S525
-rw-r--r--src/wavpack/float.c50
-rw-r--r--src/wavpack/license.txt25
-rw-r--r--src/wavpack/metadata.c105
-rw-r--r--src/wavpack/readme.txt68
-rw-r--r--src/wavpack/unpack.c785
-rw-r--r--src/wavpack/wavpack.h384
-rw-r--r--src/wavpack/words.c560
-rw-r--r--src/wavpack/wputils.c351
-rw-r--r--src/wavpack/wvfilter.c.no_compile200
40 files changed, 14796 insertions, 0 deletions
diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644
index 00000000..3332a630
--- /dev/null
+++ b/src/client.cpp
@@ -0,0 +1,599 @@
+#include <baselib/system.h>
+#include <baselib/keys.h>
+#include <baselib/mouse.h>
+#include <baselib/audio.h>
+#include <baselib/stream/file.h>
+
+#include <string.h>
+#include <math.h>
+#include "interface.h"
+
+
+#include "packet.h"
+#include "snapshot.h"
+#include "ui.h"
+
+#include "lzw.h"
+
+using namespace baselib;
+
+// --- string handling (MOVE THESE!!) ---
+void snap_encode_string(const char *src, int *dst, int length, int max_length)
+{
+	const unsigned char *p = (const unsigned char *)src;
+	
+	// handle whole int
+	for(int i = 0; i < length/4; i++)
+	{
+		*dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]);
+		p += 4;
+		dst++;
+	}
+	
+	// take care of the left overs
+	int left = length%4;
+	if(left)
+	{
+		unsigned last = 0;
+		switch(left)
+		{
+			case 3: last |= p[2]<<8;
+			case 2: last |= p[1]<<16;
+			case 1: last |= p[0]<<24;
+		}
+		*dst = last;
+	}
+}
+
+void snap_decode_string(const int *src, char *dst, int max_length)
+{
+	dbg_assert((max_length%4) == 0, "length must be power of 4");
+	for(int i = 0; i < max_length; i++)
+		dst[0] = 0;
+	
+	for(int i = 0; i < max_length/4; i++)
+	{
+		dst[0] = (*src>>24)&0xff;
+		dst[1] = (*src>>16)&0xff;
+		dst[2] = (*src>>8)&0xff;
+		dst[3] = (*src)&0xff;
+		src++;
+		dst+=4;
+	}
+	dst[-1] = 0; // make sure to zero terminate
+}
+
+// --- input wrappers ---
+static int keyboard_state[2][keys::last];
+static int keyboard_current = 0;
+static int keyboard_first = 1;
+
+void inp_mouse_relative(int *x, int *y) { mouse::position(x, y); }
+int inp_key_pressed(int key) { return keyboard_state[keyboard_current][key]; }
+int inp_key_was_pressed(int key) { return keyboard_state[keyboard_current^1][key]; }
+int inp_key_down(int key) { return inp_key_pressed(key)&&!inp_key_was_pressed(key); }
+int inp_mouse_button_pressed(int button) { return mouse::pressed(button); }
+
+void inp_update()
+{
+	if(keyboard_first)
+	{
+		// make sure to reset
+		keyboard_first = 0;
+		inp_update();
+	}
+	
+	keyboard_current = keyboard_current^1;
+	for(int i = 0; i < keys::last; i++)
+		keyboard_state[keyboard_current][i] = keys::pressed(i);
+}
+
+// --- input snapping ---
+static int input_data[MAX_INPUT_SIZE];
+static int input_data_size;
+void snap_input(void *data, int size)
+{
+	mem_copy(input_data, data, size);
+	input_data_size = size;
+}
+
+// -- snapshot handling ---
+enum
+{
+	SNAP_INCOMMING=2,
+	NUM_SNAPSHOT_TYPES=3,
+};
+
+static snapshot *snapshots[NUM_SNAPSHOT_TYPES];
+static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE];
+static int recived_snapshots;
+static int64 snapshot_start_time;
+static int64 local_start_time;
+
+float client_localtime()
+{
+	return (time_get()-local_start_time)/(float)(time_freq());
+}
+
+void *snap_get_item(int snapid, int index, snap_item *item)
+{
+	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
+	snapshot::item *i = snapshots[snapid]->get_item(index);
+	item->type = i->type();
+	item->id = i->id();
+	return (void *)i->data;
+}
+
+int snap_num_items(int snapid)
+{
+	dbg_assert(snapid >= 0 && snapid < NUM_SNAPSHOT_TYPES, "invalid snapid");
+	return snapshots[snapid]->num_items;
+}
+
+static void snap_init()
+{
+	snapshots[SNAP_INCOMMING] = (snapshot*)snapshot_data[0];
+	snapshots[SNAP_CURRENT] = (snapshot*)snapshot_data[1];
+	snapshots[SNAP_PREV] = (snapshot*)snapshot_data[2];
+	mem_zero(snapshot_data, NUM_SNAPSHOT_TYPES*MAX_SNAPSHOT_SIZE);
+	recived_snapshots = 0;
+}
+
+float snap_intratick()
+{
+	return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED);
+}
+
+void *snap_find_item(int snapid, int type, int id)
+{
+	// TODO: linear search. should be fixed.
+	for(int i = 0; i < snapshots[snapid]->num_items; i++)
+	{
+		snapshot::item *itm = snapshots[snapid]->get_item(i);
+		if(itm->type() == type && itm->id() == id)
+			return (void *)itm->data;
+	}
+	return 0x0;
+}
+
+
+int menu_loop();
+float frametime = 0.0001f;
+
+float client_frametime()
+{
+	return frametime;
+}
+
+// --- client ---
+class client
+{
+public:
+	socket_udp4 socket;
+	connection conn;
+	int64 reconnect_timer;
+	
+	int snapshot_part;
+
+	int debug_font; // TODO: rfemove this line
+
+	// data to hold three snapshots
+	// previous, 
+	char name[MAX_NAME_LENGTH];
+
+	bool fullscreen;
+
+	enum
+	{
+		STATE_OFFLINE,
+		STATE_CONNECTING,
+		STATE_LOADING,
+		STATE_ONLINE,
+		STATE_BROKEN,
+		STATE_QUIT,
+	};
+	
+	int state;
+	int get_state() { return state; }
+	void set_state(int s)
+	{
+		dbg_msg("game", "state change. last=%d current=%d", state, s);
+		state = s;
+	}
+	
+	void set_name(const char *new_name)
+	{
+		mem_zero(name, MAX_NAME_LENGTH);
+		strncpy(name, new_name, MAX_NAME_LENGTH);
+		name[MAX_NAME_LENGTH-1] = 0;
+	}
+
+	void set_fullscreen(bool flag) { fullscreen = flag; }
+	
+	void send_packet(packet *p)
+	{
+		conn.send(p);
+	}
+	
+	void send_connect()
+	{
+		recived_snapshots = 0;
+		
+		packet p(NETMSG_CLIENT_CONNECT);
+		p.write_str("dev v1", 32); // payload
+		p.write_str(name,MAX_NAME_LENGTH);
+		p.write_str("no clan", MAX_CLANNAME_LENGTH);
+		p.write_str("password", 32);
+		p.write_str("myskin", 32);
+		send_packet(&p);
+	}
+
+	void send_done()
+	{
+		packet p(NETMSG_CLIENT_DONE);
+		send_packet(&p);
+	}
+
+	void send_error(const char *error)
+	{
+		packet p(NETMSG_CLIENT_ERROR);
+		p.write_str(error, 128);
+		send_packet(&p);
+		//send_packet(&p);
+		//send_packet(&p);
+	}	
+
+	void send_input()
+	{
+		packet p(NETMSG_CLIENT_INPUT);
+		
+		p.write_int(input_data_size);
+		for(int i = 0; i < input_data_size/4; i++)
+			p.write_int(input_data[i]);
+		send_packet(&p);
+	}
+	
+	void disconnect()
+	{
+		send_error("disconnected");
+		set_state(STATE_OFFLINE);
+		map_unload();
+	}
+	
+	void connect(netaddr4 *server_address)
+	{
+		conn.init(&socket, server_address);
+		
+		// start by sending connect
+		send_connect();
+		set_state(STATE_CONNECTING);
+		reconnect_timer = time_get()+time_freq();
+	}
+	
+	bool load_data()
+	{
+		debug_font = gfx_load_texture_tga("data/debug_font.tga");
+		return true;
+	}
+	
+	void render()
+	{
+		gfx_clear(0.0f,0.0f,0.0f);
+		
+		// this should be moved around abit
+		if(get_state() == STATE_ONLINE)
+		{
+			modc_render();
+		}
+		else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING)
+		{
+			netaddr4 server_address;
+			int status = modmenu_render(&server_address, name, MAX_NAME_LENGTH);
+
+			if (status == -1)
+				set_state(STATE_QUIT);
+			else if (status)
+				connect(&server_address);
+		}
+		else if (get_state() == STATE_CONNECTING)
+		{
+			static int64 start = time_get();
+			static int tee_texture;
+			static int connecting_texture;
+			static bool inited = false;
+			
+			if (!inited)
+			{
+				tee_texture = gfx_load_texture_tga("data/gui_tee.tga");
+				connecting_texture = gfx_load_texture_tga("data/gui/connecting.tga");
+					
+				inited = true;
+			}
+
+			gfx_mapscreen(0,0,400.0f,300.0f);
+
+			float t = (time_get() - start) / (double)time_freq();
+
+			float speed = 2*sin(t);
+
+			speed = 1.0f;
+
+			float x = 208 + sin(t*speed) * 32;
+			float w = sin(t*speed + 3.149) * 64;
+
+			ui_do_image(tee_texture, x, 95, w, 64);
+			ui_do_image(connecting_texture, 88, 150, 256, 64);
+		}
+	}
+	
+	void run(netaddr4 *server_address)
+	{
+		snapshot_part = 0;
+		
+		// init graphics and sound
+		if(!gfx_init(fullscreen))
+			return;
+
+		snd_init(); // sound is allowed to fail
+		
+		// load data
+		if(!load_data())
+			return;
+
+		// init snapshotting
+		snap_init();
+		
+		// init the mod
+		modc_init();
+
+		// init menu
+		modmenu_init();
+		
+		// open socket
+		if(!socket.open(0))
+		{
+			dbg_msg("network/client", "failed to open socket");
+			return;
+		}
+
+		// connect to the server if wanted
+		if (server_address)
+			connect(server_address);
+		
+		int64 inputs_per_second = 50;
+		int64 time_per_input = time_freq()/inputs_per_second;
+		int64 game_starttime = time_get();
+		int64 lastinput = game_starttime;
+		
+		int64 reporttime = time_get();
+		int64 reportinterval = time_freq()*1;
+		int frames = 0;
+		
+		mouse::set_mode(mouse::mode_relative);
+		
+		while (1)
+		{	
+			frames++;
+			int64 frame_start_time = time_get();
+			
+			if(time_get()-lastinput > time_per_input)
+			{
+				if(get_state() == STATE_ONLINE)
+					send_input();
+				lastinput += time_per_input;
+			}
+			
+			// update input
+			inp_update();
+			
+			//
+			if(keys::pressed(keys::f1))
+				mouse::set_mode(mouse::mode_absolute);
+			if(keys::pressed(keys::f2))
+				mouse::set_mode(mouse::mode_relative);
+				
+			// pump the network
+			pump_network();
+			
+			// render
+			render();
+			
+			// swap the buffers
+			gfx_swap();
+			
+			// check conditions
+			if(get_state() == STATE_BROKEN || get_state() == STATE_QUIT)
+				break;
+
+			// be nice
+			//thread_sleep(1);
+			
+			if(reporttime < time_get())
+			{
+				unsigned sent, recved;
+				conn.counter_get(&sent, &recved);
+				dbg_msg("client/report", "fps=%.02f",
+					frames/(float)(reportinterval/time_freq()));
+				frames = 0;
+				reporttime += reportinterval;
+				conn.counter_reset();
+			}
+			
+			if (keys::pressed(keys::esc))
+				if (get_state() == STATE_CONNECTING || get_state() == STATE_ONLINE)
+					disconnect();
+
+			// update frametime
+			frametime = (time_get()-frame_start_time)/(float)time_freq();
+		}
+		
+		modc_shutdown();
+		disconnect();
+
+		modmenu_shutdown();
+		
+		gfx_shutdown();
+		snd_shutdown();
+	}
+	
+	void error(const char *msg)
+	{
+		dbg_msg("game", "error: %s", msg);
+		send_error(msg);
+		set_state(STATE_BROKEN);
+	}
+
+	void process_packet(packet *p)
+	{
+		if(p->msg() == NETMSG_SERVER_ACCEPT)
+		{
+			char map[32];
+			p->read_str(map, 32);
+			
+			if(p->is_good())
+			{
+				dbg_msg("client/network", "connection accepted, map=%s", map);
+				set_state(STATE_LOADING);
+				
+				if(map_load(map))
+				{
+					modc_entergame();
+					send_done();
+					dbg_msg("client/network", "loading done");
+					// now we will wait for two snapshots
+					// to finish the connection
+				}
+				else
+				{
+					error("failure to load map");
+				}
+			}
+		}
+		else if(p->msg() == NETMSG_SERVER_SNAP)
+		{
+			//dbg_msg("client/network", "got snapshot");
+			int num_parts = p->read_int();
+			int part = p->read_int();
+			int part_size = p->read_int();
+			
+			if(p->is_good())
+			{
+				if(snapshot_part == part)
+				{
+					p->read_raw((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, part_size);
+					snapshot_part++;
+				
+					if(snapshot_part == num_parts)
+					{
+						snapshot *tmp = snapshots[SNAP_PREV];
+						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+						snapshots[SNAP_CURRENT] = tmp;
+
+						// decompress snapshot
+						lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]);
+						
+						// apply snapshot, cycle pointers
+						/*
+						snapshot *tmp = snapshots[SNAP_PREV];
+						snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+						snapshots[SNAP_CURRENT] = snapshots[SNAP_INCOMMING];
+						snapshots[SNAP_INCOMMING] = tmp;
+						*/
+						
+						recived_snapshots++;
+						snapshot_start_time = time_get();
+						
+						// we got two snapshots until we see us self as connected
+						if(recived_snapshots == 2)
+						{
+							local_start_time = time_get();
+							set_state(STATE_ONLINE);
+						}
+						
+						if(recived_snapshots > 2)
+							modc_newsnapshot();
+						
+						snapshot_part = 0;
+					}
+
+				}
+				else
+				{
+					dbg_msg("client", "snapshot reset!");
+					snapshot_part = 0;
+				}
+			}
+		}
+		else
+		{
+			dbg_msg("server/client", "unknown packet %x", p->msg());
+		}
+	}
+	
+	void pump_network()
+	{
+		while(1)
+		{
+			packet p;
+			netaddr4 from;
+			int bytes = socket.recv(&from, p.data(), p.max_size());
+			
+			if(bytes <= 0)
+				break;
+			
+			process_packet(&p);
+		}
+		
+		//
+		if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer)
+		{
+			send_connect();
+			reconnect_timer = time_get() + time_freq();
+		}
+	}	
+};
+
+int client_main(int argc, char **argv)
+{
+	dbg_msg("client", "starting...");
+	netaddr4 server_address(127, 0, 0, 1, 8303);
+	const char *name = "nameless jerk";
+	bool connect_at_once = false;
+	bool fullscreen = true;
+
+	// init network, need to be done first so we can do lookups
+	net_init();
+
+	// parse arguments
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -c SERVER
+			i++;
+			if(net_host_lookup(argv[i], 8303, &server_address) != 0)
+				dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]);
+			else
+				connect_at_once = true;
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -n NAME
+			i++;
+			name = argv[i];
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0)
+		{
+			// -w
+			i++;
+			fullscreen = false;
+		}
+	}
+	
+	// start the server
+	client c;
+	c.set_name(name);
+	c.set_fullscreen(fullscreen);
+	c.run(connect_at_once ? &server_address : 0x0);
+	return 0;
+}
diff --git a/src/datafile.cpp b/src/datafile.cpp
new file mode 100644
index 00000000..789aa722
--- /dev/null
+++ b/src/datafile.cpp
@@ -0,0 +1,444 @@
+#include <baselib/system.h>
+#include <baselib/stream/file.h>
+
+#include "datafile.h"
+
+static const int DEBUG=0;
+
+struct item_type
+{
+	int type;
+	int start;
+	int num;
+};
+
+struct item
+{
+	int type_and_id;
+	int size;
+};
+
+struct datafile_header
+{
+	int id;
+	int version;
+	int size;
+	int swaplen;
+	int num_item_types;
+	int num_items;
+	int num_raw_data;
+	int item_size;
+	int data_size;
+};
+
+struct datafile_data
+{
+	int num_item_types;
+	int num_items;
+	int num_raw_data;
+	int item_size;
+	int data_size;
+	char start[4];
+};
+
+struct datafile_info
+{
+	item_type *item_types;
+	int *item_offsets;
+	int *data_offsets;
+
+	char *item_start;
+	char *data_start;	
+};
+
+struct datafile
+{
+	datafile_info info;
+	datafile_data data;
+};
+
+datafile *datafile_load(const char *filename)
+{
+	dbg_msg("datafile", "datafile loading. filename='%s'", filename);
+
+	baselib::file_stream file;
+	if(!file.open_r(filename))
+		return 0;
+	
+	// TODO: change this header
+	int header[4];
+	file.read(header, sizeof(header));
+	if(((header[0]>>24)&0xff) != 'D' || ((header[0]>>16)&0xff) != 'A' || (header[0]>>8)&0xff != 'T' || (header[0]&0xff)!= 'A')
+	{
+		dbg_msg("datafile", "wrong signature. %x %x %x %x", header[0], header[1], header[2], header[3]);
+		return 0;
+	}
+	
+	int version = header[1];
+	if(version != 3)
+	{
+		dbg_msg("datafile", "wrong version. version=%x", version);
+		return 0;
+	}
+	
+	unsigned size = header[2];
+	unsigned swapsize = header[3];
+	
+	if(DEBUG)
+		dbg_msg("datafile", "loading. size=%d", size);
+	
+	// TODO: use this variable for good and awesome
+	(void)swapsize;
+	
+	//map_unload();
+	datafile *df = (datafile*)mem_alloc(size+sizeof(datafile_info), 1);
+	unsigned readsize = file.read(&df->data, size);
+	if(readsize != size)
+	{
+		dbg_msg("datafile", "couldn't load the whole thing, wanted=%d got=%d", size, readsize);
+		return 0;
+	}
+	
+	// TODO: byteswap
+	//map->byteswap();
+
+	if(DEBUG)
+		dbg_msg("datafile", "item_size=%d", df->data.item_size);
+	
+	
+	df->info.item_types = (item_type *)df->data.start;
+	df->info.item_offsets = (int *)&df->info.item_types[df->data.num_item_types];
+	df->info.data_offsets = (int *)&df->info.item_offsets[df->data.num_items];
+	
+	df->info.item_start = (char *)&df->info.data_offsets[df->data.num_raw_data];
+	df->info.data_start = df->info.item_start + df->data.item_size;
+
+	if(DEBUG)
+		dbg_msg("datafile", "datafile loading done. datafile='%s'", filename);
+
+	if(DEBUG)
+	{
+		for(int i = 0; i < df->data.num_raw_data; i++)
+		{
+			void *p = datafile_get_data(df, i);
+			dbg_msg("datafile", "%d %d", (int)((char*)p - (char*)(&df->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, df->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 < df->data.num_item_types; i++)
+		{
+			dbg_msg("map", "\t%d: type=%x start=%d num=%d", i,
+				df->info.item_types[i].type,
+				df->info.item_types[i].start,
+				df->info.item_types[i].num);
+			for(int k = 0; k < df->info.item_types[i].num; k++)
+			{
+				int type, id;
+				datafile_get_item(df, df->info.item_types[i].start+k, &type, &id);
+				if(type != df->info.item_types[i].type)
+					dbg_msg("map", "\tERROR");
+			}
+		}
+	}
+		
+	return df;
+}
+
+void *datafile_get_data(datafile *df, int index)
+{
+	return df->info.data_start+df->info.data_offsets[index];
+}
+
+void *datafile_get_item(datafile *df, int index, int *type, int *id)
+{
+	item *i = (item *)(df->info.item_start+df->info.item_offsets[index]);
+	if(type)
+		*type = (i->type_and_id>>16)&0xffff; // remove sign extention
+	if(id)
+		*id = i->type_and_id&0xffff;
+	return (void *)(i+1);
+}
+
+void datafile_get_type(datafile *df, int type, int *start, int *num)
+{
+	for(int i = 0; i < df->data.num_item_types; i++)
+	{
+		if(df->info.item_types[i].type == type)
+		{
+			*start = df->info.item_types[i].start;
+			*num = df->info.item_types[i].num;
+			return;
+		}
+	}
+	
+	*start = 0;
+	*num = 0;
+}
+
+void *datafile_find_item(datafile *df, int type, int id)
+{
+	int start, num;
+	datafile_get_type(df, type, &start, &num);
+	for(int i = 0; i < num; i++)
+	{
+		int item_id;
+		void *item = datafile_get_item(df, start+i,0, &item_id);
+		if(id == item_id)
+			return item;
+	}
+	return 0;
+}
+
+int datafile_num_items(datafile *df)
+{
+	return df->data.num_items;
+}
+
+void datafile_unload(datafile *df)
+{
+	if(df)
+		mem_free(df);
+}
+
+// DATAFILE output
+struct data_info
+{
+	int size;
+	void *data;
+};
+
+struct item_info
+{
+	int type;
+	int id;
+	int size;
+	int next;
+	int prev;
+	void *data;
+};
+
+struct itemtype_info
+{
+	int num;
+	int first;
+	int last;
+};
+
+//
+struct datafile_out
+{
+	baselib::file_stream file;
+	int num_items;
+	int num_datas;
+	int num_item_types;
+	itemtype_info item_types[0xffff];
+	item_info items[1024];
+	data_info datas[1024];
+};
+
+datafile_out *datafile_create(const char *filename)
+{
+	datafile_out *df = new datafile_out;
+	if(!df->file.open_w(filename))
+	{
+		delete df;
+		return 0;
+	}
+	
+	df->num_items = 0;
+	df->num_datas = 0;
+	df->num_item_types = 0;
+	mem_zero(&df->item_types, sizeof(df->item_types));
+
+	for(int i = 0; i < 0xffff; i++)
+	{
+		df->item_types[i].first = -1;
+		df->item_types[i].last = -1;
+	}
+	
+	return df;
+}
+
+int datafile_add_item(datafile_out *df, int type, int id, int size, void *data)
+{
+	df->items[df->num_items].type = type;
+	df->items[df->num_items].id = id;
+	df->items[df->num_items].size = size;
+	
+	// copy data
+	df->items[df->num_items].data = mem_alloc(size, 1);
+	mem_copy(df->items[df->num_items].data, data, size);
+
+	if(!df->item_types[type].num) // count item types
+		df->num_item_types++;
+
+	// link
+	df->items[df->num_items].prev = df->item_types[type].last;
+	df->items[df->num_items].next = -1;
+	
+	if(df->item_types[type].last != -1)
+		df->items[df->item_types[type].last].next = df->num_items;
+	df->item_types[type].last = df->num_items;
+	
+	if(df->item_types[type].first == -1)
+		df->item_types[type].first = df->num_items;
+	
+	df->item_types[type].num++;
+		
+	df->num_items++;
+	return df->num_items-1;
+}
+
+int datafile_add_data(datafile_out *df, int size, void *data)
+{
+	df->datas[df->num_items].size = size;
+	df->datas[df->num_datas].data = data;
+	df->num_datas++;
+	return df->num_datas-1;
+}
+
+int datafile_finish(datafile_out *df)
+{
+	// we should now write this file!
+	if(DEBUG)
+		dbg_msg("datafile", "writing");
+
+	// calculate sizes
+	int itemsize = 0;
+	for(int i = 0; i < df->num_items; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "item=%d size=%d (%d)", i, df->items[i].size, df->items[i].size+sizeof(item));
+		itemsize += df->items[i].size + sizeof(item);
+	}
+	
+	int datasize = 0;
+	for(int i = 0; i < df->num_datas; i++)
+		datasize += df->datas[i].size;
+	
+	// calculate the complete size
+	int typessize = df->num_item_types*sizeof(item_type);
+	int headersize = sizeof(datafile_header);
+	int offsetsize = df->num_items*sizeof(int) + df->num_datas*sizeof(int);
+	int filesize = headersize + typessize + offsetsize + itemsize + datasize;
+	int swapsize = filesize - datasize;
+	
+	if(DEBUG)
+		dbg_msg("datafile", "num_item_types=%d typessize=%d itemsize=%d datasize=%d", df->num_item_types, typessize, itemsize, datasize);
+	
+	// construct header
+	datafile_header header;
+	header.id = ('D'<<24) | ('A'<<16) | ('T'<<8) | ('A');
+	header.version = 3;
+	header.size = filesize - 16;
+	header.swaplen = swapsize - 16;
+	header.num_item_types = df->num_item_types;
+	header.num_items = df->num_items;
+	header.num_raw_data = df->num_datas;
+	header.item_size = itemsize;
+	header.data_size = datasize;
+	
+	// TODO: apply swapping
+	// write header
+	if(DEBUG)
+		dbg_msg("datafile", "headersize=%d", sizeof(header));
+	df->file.write(&header, sizeof(header));
+	
+	// write types
+	for(int i = 0, count = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write info
+			item_type info;
+			info.type = i;
+			info.start = count;
+			info.num = df->item_types[i].num;
+			if(DEBUG)
+				dbg_msg("datafile", "writing type=%x start=%d num=%d", info.type, info.start, info.num);
+			df->file.write(&info, sizeof(info));
+			
+			count += df->item_types[i].num;
+		}
+	}
+	
+	// write item offsets
+	for(int i = 0, offset = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write all items in of this type
+			int k = df->item_types[i].first;
+			while(k != -1)
+			{
+				if(DEBUG)
+					dbg_msg("datafile", "writing item offset num=%d offset=%d", k, offset);
+				df->file.write(&offset, sizeof(offset));
+				offset += df->items[k].size + sizeof(item);
+				
+				// next
+				k = df->items[k].next;
+			}
+		}
+	}
+	
+	// write data offsets
+	for(int i = 0, offset = 0; i < df->num_datas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data offset num=%d offset=%d", i, offset);
+		df->file.write(&offset, sizeof(offset));
+		offset += df->datas[i].size;
+	}
+	
+	// write items
+	for(int i = 0; i < 0xffff; i++)
+	{
+		if(df->item_types[i].num)
+		{
+			// write all items in of this type
+			int k = df->item_types[i].first;
+			while(k != -1)
+			{
+				item itm;
+				itm.type_and_id = (i<<16)|df->items[k].id;
+				itm.size = df->items[k].size;
+				if(DEBUG)
+					dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, df->items[k].id, df->items[k].size);
+				df->file.write(&itm, sizeof(itm));
+				df->file.write(df->items[k].data, df->items[k].size);
+				
+				// next
+				k = df->items[k].next;
+			}
+		}
+	}
+	
+	// write data
+	for(int i = 0; i < df->num_datas; i++)
+	{
+		if(DEBUG)
+			dbg_msg("datafile", "writing data id=%d size=%d", i, df->datas[i].size);
+		df->file.write(df->datas[i].data, df->datas[i].size);
+	}
+
+	// free data
+	for(int i = 0; i < df->num_items; i++)
+		mem_free(df->items[i].data);
+
+	
+	delete df;
+
+	if(DEBUG)
+		dbg_msg("datafile", "done");
+	return 0;
+}
diff --git a/src/datafile.h b/src/datafile.h
new file mode 100644
index 00000000..4e49fa03
--- /dev/null
+++ b/src/datafile.h
@@ -0,0 +1,20 @@
+
+// raw datafile access
+struct datafile;
+
+// read access
+datafile *datafile_load(const char *filename);
+datafile *datafile_load_old(const char *filename);
+void *datafile_get_data(datafile *df, int index);
+void *datafile_get_item(datafile *df, int index, int *type, int *id);
+void datafile_get_type(datafile *df, int type, int *start, int *num);
+void *datafile_find_item(datafile *df, int type, int id);
+int datafile_num_items(datafile *df);
+void datafile_unload(datafile *df);
+
+// write access
+struct datafile_out;
+datafile_out *datafile_create(const char *filename);
+int datafile_add_data(datafile_out *df, int size, void *data);
+int datafile_add_item(datafile_out *df, int type, int id, int size, void *data);
+int datafile_finish(datafile_out *df);
diff --git a/src/editor.cpp b/src/editor.cpp
new file mode 100644
index 00000000..9b633730
--- /dev/null
+++ b/src/editor.cpp
@@ -0,0 +1,1229 @@
+#include <stdio.h>
+#include <baselib/system.h>
+#include <baselib/keys.h>
+#include <baselib/mouse.h>
+
+#include "interface.h"
+#include "datafile.h"
+#include "ui.h"
+
+#include "game/mapres_image.h"
+#include "game/mapres_tilemap.h"
+//#include "game/mapres_col.h"
+#include "game/mapres.h"
+#include "game/game.h"
+
+using namespace baselib;
+
+static int font_texture = 0;
+
+struct ent_type
+{
+	const char *name;
+	int id;
+	int item_id;
+};
+
+static ent_type ent_types[] = {
+	{"spawn", MAPRES_SPAWNPOINT, 0},
+	{"gun", MAPRES_ITEM, ITEM_WEAPON_GUN},
+	{"shotgun", MAPRES_ITEM, ITEM_WEAPON_SHOTGUN},
+	{"rocket", MAPRES_ITEM, ITEM_WEAPON_ROCKET},
+	{"sniper", MAPRES_ITEM, ITEM_WEAPON_SNIPER},
+	{"hammer", MAPRES_ITEM, ITEM_WEAPON_HAMMER},
+	{"health_1", MAPRES_ITEM, ITEM_HEALTH_1},
+	{"health_5", MAPRES_ITEM, ITEM_HEALTH_5},
+	{"health_10", MAPRES_ITEM, ITEM_HEALTH_10},
+	{"armor_1", MAPRES_ITEM, ITEM_ARMOR_1},
+	{"armor_5", MAPRES_ITEM, ITEM_ARMOR_5},
+	{"armor_10", MAPRES_ITEM, ITEM_ARMOR_10},
+	{"ninja!", MAPRES_ITEM, ITEM_NINJA},
+	{0, 0}
+};
+
+
+/********************************************************
+ ENTITIES                                                
+*********************************************************/
+struct entity
+{
+	int type;
+	int x, y;
+};
+
+static const int MAX_ENTITIES = 1024;
+static entity entites[MAX_ENTITIES];
+static int num_entities = 0;
+
+static int ents_count()
+{
+	return num_entities;
+}
+
+static entity *ents_get(int index)
+{
+	return &entites[index];
+}
+
+static int ents_delete(int index)
+{
+	if(index < 0 || index >= ents_count())
+		return -1;
+	num_entities--;
+	entites[index] = entites[num_entities];
+	return 0;
+}
+
+static int ents_new(int type, int x, int y)
+{
+	entites[num_entities].type = type;
+	entites[num_entities].x = x;
+	entites[num_entities].y = y;
+	num_entities++;
+	return num_entities-1;
+}
+
+/********************************************************
+ TILEMAP                                                 
+*********************************************************/
+struct tile
+{
+	unsigned char index;
+	unsigned char flags;
+};
+
+struct tilemap
+{
+	int width;
+	int height;
+	tile *tiles;
+};
+
+// allocates a new tilemap and inits the structure
+static void tilemap_new(tilemap *tm, int width, int height)
+{
+	unsigned size = sizeof(tile)*width*height;
+	mem_zero(tm, sizeof(tilemap));
+	
+	tm->width = width;
+	tm->height = height;
+	tm->tiles = (tile *)mem_alloc(size, 1);
+	mem_zero(tm->tiles, size);
+}
+
+// resizes an tilemap, copies the old data
+static void tilemap_resize(tilemap *tm, int new_width, int new_height, int snap)
+{
+	if(new_width <= 0) new_width = 1;
+	if(new_height <= 0) new_height = 1;
+		
+	new_width = (new_width+snap-1)/snap*snap;
+	new_height = (new_height+snap-1)/snap*snap;
+	
+	unsigned size = sizeof(tile)*new_width*new_height;
+	tile *newtiles = (tile *)mem_alloc(size, 1);
+	mem_zero(newtiles, size);
+
+	// copy old tiles
+	int w = new_width < tm->width ? new_width : tm->width;
+	int h = new_height < tm->height ? new_height : tm->height;
+	for(int y = 0; y < h; y++)
+		for(int x = 0; x < w; x++)
+			newtiles[y*new_width+x] = tm->tiles[y*tm->width+x];
+		
+	// free old tiles and set new values
+	mem_free(tm->tiles);
+	tm->tiles = newtiles;
+	tm->width = new_width;
+	tm->height = new_height;
+}
+
+static void tilemap_destroy(tilemap *tm)
+{
+	mem_free(tm->tiles);
+	tm->tiles = 0;
+	tm->width = 0;
+	tm->height = 0;
+}
+
+static int tilemap_blit(tilemap *dst, tilemap *src, int x, int y)
+{
+	int count = 0;
+	// TODO: performance of this could be better
+	for(int iy = 0; iy < src->height; iy++)
+		for(int ix = 0; ix < src->width; ix++)
+		{
+			if(x+ix >= dst->width || y+iy >= dst->height)
+				continue;
+			if(x+ix < 0 || y+iy < 0)
+				continue;
+			
+			count++;
+			dst->tiles[(y+iy)*dst->width + x+ix] = src->tiles[iy*src->width + ix];
+		}
+	return count;
+}
+
+/********************************************************
+ LAYERS                                                  
+*********************************************************/
+struct layer
+{
+	tilemap tm;
+	int tileset_id;
+	int visible;
+	int main_layer;
+};
+
+static const int MAX_LAYERS = 64;
+static layer layers[MAX_LAYERS];
+static int num_layers = 0;
+static int current_layer = 0;
+
+static int layers_remove(int index)
+{
+	if(index < 0 || index >= num_layers)
+		return 0;
+	
+	// free the memory
+	mem_free(layers[index].tm.tiles);
+	
+	// move the layers
+	for(int i = index; i < num_layers-1; i++)
+		layers[i] = layers[i+1];
+	num_layers--;
+	return 1;
+}
+
+static int layers_count()
+{
+	return num_layers;
+}
+
+static layer *layers_get_current()
+{
+	return &layers[current_layer];
+}
+
+static layer *layers_get(int index)
+{
+	return &layers[index];
+}
+
+static int layers_new(int w, int h)
+{
+	if(num_layers+1 >= MAX_LAYERS)
+		return -1;
+	
+	tilemap_new(&layers[num_layers].tm, w, h);
+	layers[num_layers].tileset_id = -1;
+	layers[num_layers].visible = 1;
+	layers[num_layers].main_layer = 0;
+	num_layers++;
+	return num_layers-1;
+}
+
+static int layers_swap(int index1, int index2)
+{
+	// swap the two layers
+	layer temp = layers[index1];
+	layers[index1] = layers[index2];
+	layers[index2] = temp;
+	return -1;
+}
+
+static int layers_moveup(int index)
+{
+	if(index < 0 || index >= num_layers)
+		return index;
+	
+	if(index == 0)
+		return 0;
+	
+	layers_swap(index, index-1);
+	return index-1;
+}
+
+static int layers_movedown(int index)
+{
+	if(index < 0 || index >= num_layers)
+		return index;
+	
+	if(index+1 == num_layers)
+		return index;
+	
+	layers_swap(index, index+1);
+	return index+1;
+}
+
+/********************************************************
+ TILESET                                                 
+*********************************************************/
+struct tileset
+{
+	int tex_id;
+	image_info img;
+};
+
+static const int MAX_TILESETS = 64;
+static tileset tilesets[MAX_TILESETS];
+static int num_tilesets = 0;
+
+static int tilesets_new()
+{
+	tilesets[num_tilesets].img.width = 0;
+	tilesets[num_tilesets].img.height = 0;
+	tilesets[num_tilesets].img.data = 0;
+	tilesets[num_tilesets].tex_id = -1;// gfx_load_texture_raw(img.width, img.height, img.data);
+	num_tilesets++;
+	return num_tilesets-1;
+}
+
+
+static void tilesets_clear()
+{
+	// TODO: remove texture aswell
+	for(int i = 0; i < num_tilesets; i++)
+		mem_free(tilesets[num_tilesets].img.data);
+	num_tilesets = 0;
+	
+}
+
+static int tilesets_set_img(int index, int w, int h, void *data)
+{
+	tilesets[index].img.width = w;
+	tilesets[index].img.height = h;
+	
+	if(tilesets[index].img.data)
+		mem_free(tilesets[index].img.data);
+	tilesets[index].img.data = data;
+	tilesets[index].tex_id = gfx_load_texture_raw(w, h, data);
+	return index;
+}
+
+static int tilesets_count()
+{
+	return num_tilesets;
+}
+
+static tileset *tilesets_get(int index)
+{
+	return &tilesets[index];
+}
+
+/********************************************************
+ UI                                                      
+*********************************************************/
+static void render_tilemap(tilemap *tm, float sx, float sy, float scale)
+{
+	float frac = (1.0f/512.0f); //2.0f;
+	gfx_quads_begin();
+	for(int y = 0; y < tm->height; y++)
+		for(int x = 0; x < tm->width; x++)
+		{
+			unsigned char d = tm->tiles[y*tm->width+x].index;
+			if(d)
+			{
+				gfx_quads_setsubset(
+					(d%16)/16.0f+frac,
+					(d/16)/16.0f+frac,
+					(d%16)/16.0f+1.0f/16.0f-frac,
+					(d/16)/16.0f+1.0f/16.0f-frac);
+				gfx_quads_drawTL(sx+x*scale, sy+y*scale, scale, scale);
+			}
+
+			//gfx_quads_setsubset(x/16.0f,y/16.0f,(x+1)/16.0f,(y+1)/16.0f);
+			//gfx_quads_drawTL(sx+x*w,sy+y*h,w,h);
+		}
+	gfx_quads_end();
+}
+
+/********************************************************
+ EDITOR                                                  
+*********************************************************/
+
+static tilemap brush = {0};
+static tilemap chooser = {0};
+static float world_offset_x = 0, world_offset_y = 0;
+static int world_zoom = 3;
+static const char *editor_filename = 0;
+static int editor_mode = 0; // 0 == tiles, 1 == ents
+static int editor_selected_ent = -1;
+
+static const int TILEMAPFLAG_READONLY = 1;
+static const int TILEMAPFLAG_UISPACE = 2;
+
+static int ui_do_tilemap(void *id, tilemap *tm, int flags, float x, float y, float scale)
+{
+	/*
+	int do_input = 1;
+	if(inp_key_pressed(keys::lalt) || inp_key_pressed(keys::ralt))
+		do_input = 0;*/
+	
+	
+	float mx = ui_mouse_world_x();
+	float my = ui_mouse_world_y();
+	if(flags&TILEMAPFLAG_UISPACE)
+	{
+		mx = ui_mouse_x();
+		my = ui_mouse_y();
+	}
+	
+	int tmx = (int)((mx-x)/scale); // tilemap x
+	int tmy = (int)((my-y)/scale); // tilemap y
+	
+	static int start_tmx, start_tmy;
+	static int grabbing = 0;
+	
+	//float start_tmx_wx = start_tmx*scale+x;
+	//float start_tmx_wy = start_tmy*scale+y;
+	
+	int select_x = 0;
+	int select_y = 0;
+	int select_w = 1;
+	int select_h = 1;
+	float select_wx = 0;
+	float select_wy = 0;
+	float select_ww = 0;
+	float select_wh = 0;
+	
+	if(ui_hot_item() == id)
+	{
+		int x0 = start_tmx;
+		int y0 = start_tmy;
+		int x1 = tmx;
+		int y1 = tmy;
+		
+		if(x1 < x0)
+		{
+			int tmp = x1;
+			x1 = x0;
+			x0 = tmp;
+		}
+		
+		if(y1 < y0)
+		{
+			int tmp = y1;
+			y1 = y0;
+			y0 = tmp;
+		}
+		
+		select_w = x1-x0;
+		select_h = y1-y0;
+		select_w++;
+		select_h++;
+		select_x = x0;
+		select_y = y0;
+		
+		select_wx = select_x*scale+x;
+		select_wy = select_y*scale+y;
+		select_ww = select_w*scale;
+		select_wh = select_h*scale;
+	}
+	// ui_do_tilemap always tries to steal the focus
+	ui_set_hot_item(id);
+	
+	// render the tilemap
+	render_tilemap(tm, x, y, scale);
+	
+	if(ui_hot_item() == id)
+	{
+		if(brush.tiles != 0)
+		{
+			// draw brush
+			render_tilemap(&brush, (tmx-brush.width/2)*scale, (tmy-brush.height/2)*scale, scale);
+			
+			gfx_texture_set(-1);
+			gfx_blend_additive();
+			gfx_quads_begin();
+			gfx_quads_setcolor(1.0f, 0.0f, 0.0f, 0.25f);
+			gfx_quads_drawTL((tmx-brush.width/2)*scale, (tmy-brush.height/2)*scale, brush.width*scale, brush.height*scale);
+			gfx_quads_end();
+			gfx_blend_normal();
+		}
+
+		if(grabbing == 0)
+		{
+			if(ui_mouse_button(0))
+			{
+				//grabbing = 1;
+				start_tmx = (int)((mx-x)/scale);
+				start_tmy = (int)((my-y)/scale);
+				if(brush.tiles == 0)
+				{
+					dbg_msg("editor", "grabbing...");
+					grabbing = 1; // grab tiles
+				}
+				else
+				{
+					// paint
+					if(!(flags&TILEMAPFLAG_READONLY))
+					{
+						layer *l = layers_get_current();
+						int px = tmx-brush.width/2;
+						int py = tmy-brush.height/2;
+						tilemap_blit(&l->tm, &brush, px, py);
+						//dbg_msg("editor", "painted %d tiles at (%d,%d)", c, px, py);
+					}
+				}
+			}
+		}
+		else
+		{
+			gfx_texture_set(-1);
+			gfx_blend_additive();
+			gfx_quads_begin();
+			gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.25f);
+			gfx_quads_drawTL(select_wx, select_wy, select_ww, select_wh);
+			gfx_quads_end();
+			gfx_blend_normal();
+			
+			if(!ui_mouse_button(0))
+			{
+				grabbing = 0;
+				
+				if(brush.tiles == 0)
+				{
+					// create brush
+					dbg_msg("editor", "creating brush w=%d h=%d p0=(%d,%d)", select_w, select_h, select_x, select_y);
+					tilemap_new(&brush, select_w, select_h);
+					
+					// copy data
+					for(int y = 0; y < select_h; y++)
+						for(int x = 0; x < select_w; x++)
+							brush.tiles[y*select_w+x] = tm->tiles[(select_y+y)*tm->width + select_x+x];
+				}
+			}
+		}
+	}
+	
+	// raw rect around tilemap
+	gfx_texture_set(-1);
+	gfx_quads_begin();
+	float w = tm->width*scale;
+	float h = tm->height*scale;
+	gfx_quads_drawTL(x-2, y-2, 2, h+4);
+	gfx_quads_drawTL(x-2, y-2, w+4, 2);
+	gfx_quads_drawTL(x+w, y-2, 2, h+4);
+	gfx_quads_drawTL(x-2, y+h, w+4, 2);
+	gfx_quads_end();
+	
+	return 0;
+}
+
+static int ui_do_entity(void *id, entity *ent, int selected)
+{
+	float x = (float)ent->x;
+	float y = (float)ent->y;
+	float w = 16;
+	float h = 16;
+	
+	float mx = ui_mouse_world_x();
+	float my = ui_mouse_world_y();
+	
+	int inside = 0;
+	int r = 0;
+	if(mx > x-w/2 && mx < x+w/2 && my > y-h/2 && my < y+h/2)
+		inside = 1;
+	
+	if(inside)
+		ui_set_hot_item(id);
+	
+	if(ui_hot_item() == id && ui_mouse_button(0))
+	{
+		ui_set_active_item(id);
+		r = 1;
+	}
+	
+	if(ui_active_item() == id)
+	{
+		if(!ui_mouse_button(0))
+			ui_set_active_item(0);
+		ent->x = (int)ui_mouse_world_x();
+		ent->y = (int)ui_mouse_world_y();
+		ent->x = (ent->x/32)*32+16;
+		ent->y = (ent->y/32)*32+16;
+	}
+	
+	// raw rect around tilemap
+	gfx_texture_set(-1);
+	gfx_quads_begin();
+	if(selected)
+		gfx_quads_setcolor(1.0f, 0.5f, 0.5f, 0.95f);
+	else if(ui_hot_item() == id)
+		gfx_quads_setcolor(1.0f, 1.0f, 1.0f, 0.95f);
+	else
+		gfx_quads_setcolor(0.75f, 0.75f, 0.75f, 0.95f);
+		
+	gfx_quads_drawTL(x-w/2, y-w/2, w, h);
+	gfx_quads_end();
+	
+    gfx_texture_set(font_texture);
+	if(ent->type >= 0)
+		gfx_quads_text(x-4, y-h/2-4, 24.0f, ent_types[ent->type].name);
+	
+	return r;
+}
+
+
+static int editor_reset()
+{
+	// delete all layers
+	while(layers_count())
+		layers_remove(layers_count()-1);
+	
+	//layers_new(50, 50);
+	tilemap_destroy(&brush);
+	current_layer = 0;
+	
+	tilesets_clear();
+	
+	// init chooser
+	static tile chooser_tiles[256];
+	for(int i = 0; i < 256; i++)
+	{
+		chooser_tiles[i].index = i;
+		chooser_tiles[i].flags = 0;
+	}
+	
+	chooser.width = 16;
+	chooser.height = 16;
+	chooser.tiles = chooser_tiles;
+	
+	return 0;
+}
+
+void draw_editor_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra)
+{
+    gfx_blend_normal();
+    gfx_texture_set(-1);
+    gfx_quads_begin(); 
+    if(ui_hot_item() == id)
+        gfx_quads_setcolor(1,1,1,1);
+    else if(checked)
+        gfx_quads_setcolor(0.75f,0.5f,0.5f,1);
+    else
+        gfx_quads_setcolor(0.5f,0.5f,0.5f,1);
+
+    gfx_quads_drawTL(x,y,w,h);
+    gfx_quads_end();
+    gfx_texture_set(font_texture);
+    gfx_quads_text(x, y, 6.2f, text);
+}
+
+static int editor_loadimage = -1;
+
+static void editor_listdir_callback(const char *name, int is_dir, void *user)
+{
+	if(name[0] == '.') // skip this shit!
+		return;
+	
+	int *y = (int*)user;
+	if(ui_do_button((void*)(*y + 1), name, 0, 10, 10 + *y * 8, 100, 6, draw_editor_button))
+	{
+		char buf[512];
+		sprintf(buf, "tilesets/%s", name);
+		
+		image_info img;
+		if(!gfx_load_tga(&img, buf))
+			return;
+		
+		tilesets_set_img(editor_loadimage, img.width, img.height, img.data);
+		editor_loadimage = -1;
+	}
+	*y += 1;
+}
+
+static void editor_render_loadfile_dialog()
+{
+	gfx_clear(0.2f,0.2f,0.8f);
+	// GUI coordsys
+	gfx_mapscreen(0,0,400.0f,300.0f);
+	
+	int index = 0;
+	fs_listdir("tilesets", editor_listdir_callback, &index);
+}
+
+static void editor_render()
+{
+	if(editor_loadimage != -1)
+	{
+		editor_render_loadfile_dialog();
+		return;
+	}
+	
+	// background color
+	gfx_clear(0.2f,0.2f,0.8f);
+	
+	// world coordsys
+	float zoom = world_zoom;
+	gfx_mapscreen(world_offset_x,world_offset_y,world_offset_x+400.0f*zoom,world_offset_y+300.0f*zoom);
+
+	for(int i = 0; i < layers_count(); i++)
+	{
+		layer *l = layers_get(i);
+		
+		gfx_texture_set(-1);
+		if(l->tileset_id >= 0 && l->tileset_id < tilesets_count())
+			gfx_texture_set(tilesets_get(l->tileset_id)->tex_id);
+		
+		if(editor_mode == 0)
+		{
+			if(l == layers_get_current())
+			{
+				// do current layer
+				ui_do_tilemap(&l->tm, &l->tm, 0, 0, 0, 32.0f);
+			}
+			else if(l->visible)
+			{
+				// render layer
+				render_tilemap(&l->tm, 0, 0, 32.0f);
+			}
+		}
+		else
+			render_tilemap(&l->tm, 0, 0, 32.0f);			
+	}
+
+	if(editor_mode == 1)
+	{
+		// ents mode
+		for(int i = 0; i < ents_count(); i++)
+		{
+			if(ui_do_entity(ents_get(i), ents_get(i), i == editor_selected_ent))
+				editor_selected_ent = i;
+			
+		}
+	}
+	
+	// GUI coordsys
+	gfx_mapscreen(0,0,400.0f,300.0f);
+
+	// toolbox
+	float toolbox_width = 50.0f;
+	
+	if(editor_mode == 0)
+	{
+		float layerbox_x = 0;
+		float layerbox_y = 0;
+		int count = 1;
+		int main_layer = -1;
+		for(int i = 0; i < layers_count(); i++)
+		{
+			layer *l = layers_get(i);
+			char buf[128];
+			if(l->main_layer)
+			{
+				main_layer = i;
+				sprintf(buf, "Main Layer (%dx%d)", l->tm.width, l->tm.height);
+				count = 1;
+			}
+			else
+			{
+				if(main_layer == -1)
+					sprintf(buf, "Background %d (%dx%d)", count, l->tm.width, l->tm.height);
+				else
+					sprintf(buf, "Foreground %d (%dx%d)", count, l->tm.width, l->tm.height);
+				count++;
+			}
+			
+			// show / hide layer
+			const char *text = " ";
+			if(layers_get(i)->visible)
+				text = "V";
+
+			if(ui_do_button(&layers_get(i)->visible, text, 0, layerbox_x, layerbox_y+i*8, 6, 6, draw_editor_button))
+				layers_get(i)->visible = layers_get(i)->visible^1;
+				
+			if(ui_do_button(&layers_get(i)->tileset_id, buf, current_layer == i, layerbox_x+8, layerbox_y+i*8, toolbox_width-8, 6, draw_editor_button))
+			{
+				// select layer
+				current_layer = i;
+			}
+		}
+		
+		// draw buttons
+		{
+			static int push_button, pull_button;
+			float y = 150;
+			float x = 0;
+			if(ui_do_button(&push_button, "push", 0, x, y, toolbox_width, 6, draw_editor_button))
+				current_layer = layers_moveup(current_layer);
+			y += 7;
+			
+			if(ui_do_button(&pull_button, "pull", 0, x, y, toolbox_width, 6, draw_editor_button))
+				current_layer = layers_movedown(current_layer);
+			y += 10;
+
+			static int w_inc, w_dec;
+			int resize_amount = 10;
+			if(ui_do_button(&w_dec, "width-", 0, x, y, toolbox_width, 6, draw_editor_button))
+				tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width-resize_amount, layers_get_current()->tm.height, resize_amount);
+			y += 7;
+			if(ui_do_button(&w_inc, "width+", 0, x, y, toolbox_width, 6, draw_editor_button))
+				tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width+resize_amount, layers_get_current()->tm.height, resize_amount);
+			y += 10;
+
+			static int h_inc, h_dec;
+			if(ui_do_button(&h_dec, "height-", 0, x, y, toolbox_width, 6, draw_editor_button))
+				tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width, layers_get_current()->tm.height-resize_amount, resize_amount);
+			y += 7;
+			if(ui_do_button(&h_inc, "height+", 0, x, y, toolbox_width, 6, draw_editor_button))
+				tilemap_resize(&layers_get_current()->tm, layers_get_current()->tm.width, layers_get_current()->tm.height+resize_amount, resize_amount);
+			y += 10;
+		}
+
+
+		float tilesetsbox_x = 0;
+		float tilesetsbox_y = 230;
+		for(int i = 0; i < tilesets_count(); i++)
+		{
+			char buf[128];
+			sprintf(buf, "#%d %dx%d", i, tilesets_get(i)->img.width, tilesets_get(i)->img.height);
+			if(ui_do_button(&tilesets_get(i)->img, "L", layers_get(current_layer)->tileset_id == i, tilesetsbox_x, tilesetsbox_y+i*7, 6, 6, draw_editor_button))
+			{
+				// load image
+				editor_loadimage = i;
+			}
+			
+			if(ui_do_button(tilesets_get(i), buf, layers_get(current_layer)->tileset_id == i, tilesetsbox_x+8, tilesetsbox_y+i*7, toolbox_width-8, 6, draw_editor_button))
+			{
+				// select tileset for layer
+				dbg_msg("editor", "settings tileset %d=i", current_layer, i);
+				layers_get(current_layer)->tileset_id = i;
+			}
+		}
+
+		// (add) button for tilesets
+		static int load_tileset;
+		if(ui_do_button(&load_tileset, "(Add)", 0, tilesetsbox_x, tilesetsbox_y+tilesets_count()*7+3, toolbox_width, 6, draw_editor_button))
+			tilesets_new();
+		
+		if(brush.tiles != 0)
+		{
+			// right mouse button or C clears the brush
+			if(ui_mouse_button(1) || inp_key_pressed('C'))
+				tilemap_destroy(&brush);
+		}
+		
+		if(inp_key_pressed(keys::space))
+		{
+			// render chooser
+			float chooser_x = 0;
+			float chooser_y = 0;
+			
+			gfx_texture_set(-1);
+			layer *l = layers_get_current();
+			if(l && l->tileset_id >= 0 && l->tileset_id < tilesets_count())
+				gfx_texture_set(tilesets_get(l->tileset_id)->tex_id);
+			ui_do_tilemap(&chooser, &chooser, TILEMAPFLAG_READONLY|TILEMAPFLAG_UISPACE, chooser_x, chooser_y, 16.0f);
+		}
+	}
+	else
+	{
+		int current_type = -1;
+		if(editor_selected_ent >= 0 && editor_selected_ent < ents_count())
+			current_type = ents_get(editor_selected_ent)->type;
+		
+		float y = 0;
+		for(int i = 0; ent_types[i].name; i++)
+		{
+			if(ui_do_button(&ent_types[i], ent_types[i].name, current_type==i, 0, y, toolbox_width, 6, draw_editor_button))
+			{
+				if(editor_selected_ent >= 0 && editor_selected_ent < ents_count())
+					ents_get(editor_selected_ent)->type = i;
+			}
+			y += 8;
+		}
+
+		y += 8;
+		static int add, del;
+		if(ui_do_button(&add, "Add", 0, 0, y, toolbox_width, 6, draw_editor_button))
+		{
+			int x = (int)(world_offset_x+400*zoom/2)/32*32+16;
+			int y = (int)(world_offset_y+300*zoom/2)/32*32+16;
+			ents_new(0, x, y);
+		}
+		
+		y += 8;
+		if(ui_do_button(&del, "Del", 0, 0, y, toolbox_width, 6, draw_editor_button))
+			ents_delete(editor_selected_ent);
+	}
+}
+
+int editor_load(const char *filename)
+{
+	datafile *df = datafile_load(filename);
+	if(!df)
+		return 0;
+	
+	// load tilesets
+	{
+		int start, count;
+		datafile_get_type(df, MAPRES_IMAGE, &start, &count);
+		for(int i = 0; i < count; i++)
+		{
+			mapres_image *img = (mapres_image *)datafile_get_item(df, start+i, 0, 0);
+			void *data = datafile_get_data(df, img->image_data);
+			int id = tilesets_new();
+			void *data_cpy = mem_alloc(img->width*img->height*4, 1);
+			mem_copy(data_cpy, data, img->width*img->height*4);
+			tilesets_set_img(id, img->width, img->height, data_cpy);
+		}
+	}
+	
+	// load tilemaps
+	{
+		int start, num;
+		datafile_get_type(df, MAPRES_TILEMAP, &start, &num);
+		for(int t = 0; t < num; t++)
+		{
+			mapres_tilemap *tmap = (mapres_tilemap *)datafile_get_item(df, start+t,0,0);
+			//unsigned char *data = (unsigned char *)datafile_get_data(df, tmap->data);
+			
+			layer *l = layers_get(layers_new(tmap->width, tmap->height));
+			mem_copy(l->tm.tiles, datafile_get_data(df, tmap->data), tmap->width*tmap->height*2);
+			l->tileset_id = tmap->image;
+			l->main_layer = tmap->main;
+			
+			// force a main layer
+			if(num == 1)
+				l->main_layer = 1;
+		}
+	}
+	
+	// load entities
+	{
+		int type = -1;
+		for(int i = 0; ent_types[i].name; i++)
+		{
+			if(ent_types[i].id == MAPRES_SPAWNPOINT)
+			{
+				type = i;
+				break;
+			}
+		}
+		
+		int start, num;
+		datafile_get_type(df, MAPRES_SPAWNPOINT, &start, &num);
+		for(int t = 0; t < num; t++)
+		{
+			mapres_spawnpoint *sp = (mapres_spawnpoint *)datafile_get_item(df, start+t,0,0);
+			ents_new(type, sp->x, sp->y);
+		}
+	}
+	
+	{
+		int start, num;
+		datafile_get_type(df, MAPRES_ITEM, &start, &num);
+		for(int t = 0; t < num; t++)
+		{
+			mapres_item *it = (mapres_item *)datafile_get_item(df, start+t,0,0);
+			
+			int type = -1;
+			for(int i = 0; ent_types[i].name; i++)
+			{
+				if(ent_types[i].id == MAPRES_ITEM && ent_types[i].item_id == it->type)
+				{
+					dbg_msg("editor", "i type=%x mapped=%d", it->type, i);
+					type = i;
+					break;
+				}
+			}
+		
+			ents_new(type, it->x, it->y);
+		}
+	}	
+	return 1;
+}
+
+int editor_save(const char *filename)
+{
+	datafile_out *df = datafile_create(filename);
+
+	// add tilesets
+	for(int i = 0; i < tilesets_count(); i++)
+	{
+		mapres_image img;
+		tileset *ts = tilesets_get(i);
+		img.width = ts->img.width;
+		img.height = ts->img.height;
+		img.image_data = datafile_add_data(df, ts->img.width*ts->img.height*4, ts->img.data);
+		datafile_add_item(df, MAPRES_IMAGE, i, sizeof(img), &img);
+	}
+	
+	// add tilemaps
+	for(int i = 0; i < layers_count(); i++)
+	{
+		layer *l = layers_get(i);
+		mapres_tilemap tm;
+		tm.image = l->tileset_id;
+		tm.width = l->tm.width;
+		tm.height = l->tm.height;
+		tm.x = 0;
+		tm.y = 0;
+		tm.main = l->main_layer;
+		tm.scale = 1<<16;
+		tm.data = datafile_add_data(df, l->tm.width*l->tm.height*2, l->tm.tiles);
+		datafile_add_item(df, MAPRES_TILEMAP, i, sizeof(tm), &tm);
+	}
+	
+	// add collision
+	char *collisiondata = 0x0;
+	for(int i = 0; i < layers_count(); i++)
+	{
+		layer *l = layers_get(i);
+		if(l->main_layer)
+		{
+			mapres_collision col;
+			col.width = l->tm.width;
+			col.height = l->tm.height;
+			
+			collisiondata = (char *)mem_alloc(col.width*col.height, 1);
+			for(int y = 0, c = 0; y < col.height; y++)
+				for(int x = 0; x < col.width; x++, c++)
+				{
+					if(l->tm.tiles[c].index)
+						collisiondata[c] = 1;
+					else
+						collisiondata[c] = 0;
+				}
+			
+			col.data_index = datafile_add_data(df, col.width*col.height, collisiondata);
+			datafile_add_item(df, MAPRES_COLLISIONMAP, 0, sizeof(col), &col);
+			break;
+		}
+	}
+	
+	// add spawnpoints
+	for(int i = 0, id = 0; i < ents_count(); i++)
+	{
+		entity *ent = ents_get(i);
+		if(ent->type >= 0 && ent_types[ent->type].id == MAPRES_SPAWNPOINT)
+		{
+			mapres_spawnpoint sp;
+			sp.x = ent->x;
+			sp.y = ent->y;
+			sp.type = 0;
+			datafile_add_item(df, MAPRES_SPAWNPOINT, id, sizeof(sp), &sp);
+			id++;
+		}
+	}
+
+	// add items
+	for(int i = 0, id = 0; i < ents_count(); i++)
+	{
+		entity *ent = ents_get(i);
+		if(ent->type >= 0 && ent_types[ent->type].id == MAPRES_ITEM)
+		{
+			mapres_item it;
+			it.x = ent->x;
+			it.y = ent->y;
+			it.type = ent_types[ent->type].item_id;
+			dbg_msg("editor", "i mapped=%d type=%x", ent->type, it.type);
+			datafile_add_item(df, MAPRES_ITEM, id, sizeof(it), &it);
+			id++;
+		}
+	}
+	
+	// finish adn clean up
+	datafile_finish(df);
+	mem_free(collisiondata);
+	
+	return 0;
+}
+
+static int editor_loop()
+{
+	int mouse_x = 0;
+	int mouse_y = 0;
+	
+	mouse::set_mode(mouse::mode_relative);
+	
+	while(!inp_key_pressed(keys::esc))
+	{	
+		// update input
+		inp_update();
+		
+		// handle mouse movement
+		float mx, my, mwx, mwy;
+		int rx, ry;
+		{
+			inp_mouse_relative(&rx, &ry);
+			mouse_x += rx;
+			mouse_y += ry;
+			if(mouse_x < 0) mouse_x = 0;
+			if(mouse_y < 0) mouse_y = 0;
+			if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth();
+			if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight();
+
+			// update the ui
+			mx = (mouse_x/(float)gfx_screenwidth())*400.0f;
+			my = (mouse_y/(float)gfx_screenheight())*300.0f;
+			mwx = world_offset_x+mx*world_zoom; // adjust to zoom and offset
+			mwy = world_offset_y+my*world_zoom; // adjust to zoom and offset
+			
+			int buttons = 0;
+			if(inp_mouse_button_pressed(0)) buttons |= 1;
+			if(inp_mouse_button_pressed(1)) buttons |= 2;
+			if(inp_mouse_button_pressed(2)) buttons |= 4;
+			
+			ui_update(mx,my,mwx,mwy,buttons);
+		}
+		
+		//
+		editor_render();
+
+		if(inp_key_pressed(keys::lalt) || inp_key_pressed(keys::ralt))
+		{
+			static int moveid;
+			ui_set_hot_item(&moveid);
+			if(inp_mouse_button_pressed(0))
+			{
+				world_offset_x -= rx*2;
+				world_offset_y -= ry*2;
+			}
+		}
+		
+		
+		// render butt ugly mouse cursor
+		gfx_texture_set(-1);
+		gfx_quads_begin();
+		gfx_quads_setcolor(0,0,0,1);
+		gfx_quads_draw_freeform(mx,my,mx,my,
+								mx+7,my,
+								mx,my+7);
+		gfx_quads_setcolor(1,1,1,1);
+		gfx_quads_draw_freeform(mx+1,my+1,mx+1,my+1,
+								mx+5,my+1,
+								mx+1,my+5);
+		gfx_quads_end();
+		
+		// swap the buffers
+		gfx_swap();
+		
+		//
+		if(keys::pressed(keys::f1))
+			mouse::set_mode(mouse::mode_absolute);
+		if(keys::pressed(keys::f2))
+			mouse::set_mode(mouse::mode_relative);
+
+
+		// mode switch
+		if(inp_key_down(keys::tab))
+			editor_mode ^= 1;
+		
+		// zoom in
+		if(inp_key_down(keys::kp_add))
+		{
+			world_zoom--;
+			if(world_zoom < 3)
+				world_zoom = 3;
+		}
+		
+		// zoom out
+		if(inp_key_down(keys::kp_subtract))
+		{
+			world_zoom++;
+			if(world_zoom > 8)
+				world_zoom = 8;
+		}
+		
+		if(inp_key_pressed(keys::lctrl) || inp_key_pressed(keys::rctrl))
+		{
+			if(inp_key_down('L'))
+			{
+				int w = 50, h = 50;
+				for(int i = 0; i < layers_count(); i++)
+				{
+					layer *l = layers_get(i);	
+					if(l->main_layer)
+					{
+						w = l->tm.width;
+						h = l->tm.height;
+						break;
+					}
+				}					
+				// copy main layer size
+				layers_new(w, h);
+			}
+			
+			if(inp_key_down('S'))
+			{
+				dbg_msg("editor", "save");
+				editor_save(editor_filename);
+			}
+
+		}
+		
+		if(inp_key_down(keys::f5))
+		{
+			dbg_msg("editor", "quick save");
+			editor_save("quicksave.map");
+		}
+		
+		if(inp_key_down(keys::f8))
+		{
+			dbg_msg("editor", "quick load");
+			int s = current_layer;
+			editor_reset();
+			editor_load("quicksave.map");
+			current_layer = s;
+			if(current_layer >= layers_count())
+				current_layer = layers_count();
+				
+		}
+		
+		// be nice
+		thread_sleep(1);
+	}
+	
+	return 0;
+}
+
+
+int editor_main(int argc, char **argv)
+{
+	dbg_msg("editor", "starting...");
+	
+	// parse arguments
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -e NAME
+			i++;
+			editor_filename = argv[i];
+		}
+	}
+	
+	if(!editor_filename)
+	{
+		dbg_msg("editor", "no filename given");
+		return -1;
+	}
+	
+	if(!gfx_init(false))
+		return -1;
+	
+	// reset and start
+	font_texture = gfx_load_texture_tga("data/debug_font.tga");
+	editor_reset();
+
+	// load or new
+	if(!editor_load(editor_filename))
+	{
+		layer *l = layers_get(layers_new(50, 50));
+		l->main_layer = 1;
+	}
+
+	/*
+	ents_new(0, 10, 10);
+	ents_new(0, 10, 10);
+	ents_new(0, 10, 10);
+	ents_new(0, 10, 10);
+	*/
+	
+	editor_loop();
+	
+	return 0;
+}
diff --git a/src/game/game.h b/src/game/game.h
new file mode 100644
index 00000000..a1817eea
--- /dev/null
+++ b/src/game/game.h
@@ -0,0 +1,231 @@
+#include <baselib/system.h>
+#include <baselib/vmath.h>
+#include <math.h>
+#include "../interface.h"
+#include "mapres_col.h"
+
+// Don't tweak :)
+const float pi = 3.14159265358979f;
+
+#define LERP(a,b,t) (a + (b-a) * t)
+#define min(a, b) ( a > b ? b : a)
+#define max(a, b) ( a > b ? a : b)
+
+inline baselib::vec2 get_direction(int angle)
+{
+	float a = angle/256.0f;
+	return baselib::vec2(cosf(a), sinf(a));
+}
+
+inline float get_angle(baselib::vec2 dir)
+{
+	float a = atan(dir.y/dir.x);
+	if(dir.x < 0)
+		a = a+pi;
+	return a;
+}
+
+inline bool col_check_point(float x, float y) { return col_check_point((int)x, (int)y); }
+inline bool col_check_point(baselib::vec2 p) { return col_check_point(p.x, p.y); }
+
+// Network stuff
+
+enum
+{
+	OBJTYPE_NULL=0,
+	OBJTYPE_PLAYER,
+	OBJTYPE_PROJECTILE,
+	OBJTYPE_POWERUP,
+	EVENT_EXPLOSION,
+	EVENT_HEALTHMOD,
+	EVENT_SOUND,
+	EVENT_SMOKE,
+};
+
+enum
+{
+	EMOTE_NORMAL=0,
+	EMOTE_BLINK,
+	EMOTE_WINK,
+	EMOTE_PAIN,
+	EMOTE_HAPPY,
+};
+
+struct player_input
+{
+	int left;
+	int right;
+	int angle;
+	int jump;
+	int fire;
+	int hook;
+	int blink;
+	int activeweapon;
+};
+
+struct ev_explosion
+{
+	int x, y;
+};
+
+struct ev_sound
+{
+	int x, y;
+	int sound; // if (0x80000000 flag is set -> looping) if (0x40000000 is set -> stop looping
+};
+
+struct ev_healthmod
+{
+	int x, y;
+	int amount;
+};
+
+struct obj_projectile
+{
+	int type;
+	int x, y;
+	int vx, vy;
+};
+
+struct obj_powerup
+{
+	int type;
+	int subtype;
+	int x, y;
+	int vx, vy;
+};
+
+struct obj_player
+{
+	int name[8];
+	
+	int local;
+	int clientid;
+
+	int health;
+	int armor;
+	int ammocount;
+	
+	int x, y;
+	int vx, vy;
+	int angle;
+	
+	// current active weapon
+	int weapon;
+	// current active modifier
+	int modifier;
+
+	// num attack ticks left of current attck
+	int attackticks;
+	int attacklen;
+	int visualtimeattack;
+	
+	int score;
+	int emote;
+	
+	int hook_active;
+	int hook_x, hook_y;
+};
+
+enum
+{
+	WEAPON_TYPE_GUN			= 0,
+	WEAPON_TYPE_ROCKET		= 1,
+	WEAPON_TYPE_SHOTGUN		= 2,
+	WEAPON_TYPE_MELEE		= 3,
+	WEAPON_NUMWEAPONS,
+	//WEAPON_TYPE_SNIPER		= 2,
+
+	POWERUP_TYPE_HEALTH			= 0,
+	POWERUP_TYPE_ARMOR			= 1,
+	POWERUP_TYPE_WEAPON			= 2,
+	POWERUP_TYPE_NINJA			= 3,
+	POWERUP_TYPE_TIMEFIELD		= 4,
+	POWERUP_TYPE_NUMPOWERUPS,
+
+	PLAYER_MAXHEALTH			= 10,
+	PLAYER_MAXARMOR				= 10,
+
+	MODIFIER_TYPE_NINJA			= 0,
+	MODIFIER_TYPE_TIMEFIELD		= 1,
+	MODIFIER_NUMMODIFIERS,
+};
+
+
+struct mapres_spawnpoint
+{
+	int x, y;
+	int type;
+};
+
+struct mapres_item
+{
+	int x, y;
+	int type;
+};
+
+enum
+{
+	MAPRES_SPAWNPOINT=1,
+	MAPRES_ITEM=2,
+	
+	ITEM_NULL=0,
+	ITEM_WEAPON_GUN=0x00010001,
+	ITEM_WEAPON_SHOTGUN=0x00010002,
+	ITEM_WEAPON_ROCKET=0x00010003,
+	ITEM_WEAPON_SNIPER=0x00010004,
+	ITEM_WEAPON_HAMMER=0x00010005,
+	ITEM_HEALTH_1 =0x00020001,
+	ITEM_HEALTH_5 =0x00020005,
+	ITEM_HEALTH_10=0x00020010,
+	ITEM_ARMOR_1=0x00030001,
+	ITEM_ARMOR_5=0x00030005,
+	ITEM_ARMOR_10=0x00030010,
+	ITEM_NINJA=0x00040001,
+};
+
+// sound categories and stuff
+enum
+{
+	SOUND_FIRE_GUN = 0,
+	SOUND_FIRE_SHOTGUN,
+	SOUND_FIRE_ROCKET,
+	SOUND_FIRE_MELEE,
+	SOUND_FIRE_NINJA,
+
+
+	// impacts with world
+	SOUND_IMPACT_PROJECTILE_GUN,
+	SOUND_IMPACT_PROJECTILE_SHOTGUN,
+	SOUND_IMPACT_PROJECTILE_ROCKET,
+
+
+	// chain ?
+
+	// Player movement
+	SOUND_PLAYER_JUMP,
+	SOUND_PLAYER_HURT_SHORT,
+	SOUND_PLAYER_HURT_LONG,
+	SOUND_PLAYER_SPAWN,
+	SOUND_PLAYER_CHAIN_LOOP,
+	SOUND_PLAYER_CHAIN_IMPACT,
+	SOUND_PLAYER_IMPACT,
+	SOUND_PLAYER_IMPACT_NINJA,
+	SOUND_PLAYER_DIE,
+	SOUND_PLAYER_SWITCHWEAPON,
+	SOUND_PLAYER_EQUIP,
+	SOUND_PLAYER_LAND,
+
+	SOUND_NUMSOUNDS,
+
+
+	// extra defs (for future?)
+	SOUND_EQUIP_GUN = SOUND_PLAYER_EQUIP,
+	SOUND_EQUIP_ROCKET = SOUND_PLAYER_EQUIP,
+	SOUND_EQUIP_SHOTGUN = SOUND_PLAYER_EQUIP,
+	SOUND_EQUIP_MELEE = SOUND_PLAYER_EQUIP,
+
+	SOUND_LOOPFLAG_STARTLOOP = 0x80000000,
+	SOUND_LOOPFLAG_STOPLOOP = 0x40000000,
+	SOUND_MASK = ~(SOUND_LOOPFLAG_STARTLOOP | SOUND_LOOPFLAG_STOPLOOP),
+};
diff --git a/src/game/game_client.cpp b/src/game/game_client.cpp
new file mode 100644
index 00000000..6e431591
--- /dev/null
+++ b/src/game/game_client.cpp
@@ -0,0 +1,1992 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include "game.h"
+#include "mapres_image.h"
+#include "mapres_tilemap.h"
+
+using namespace baselib;
+
+static int texture_char_default = 0;
+static int texture_game = 0;
+static int texture_weapon = 0;
+static int texture_sun = 0;
+static int texture_particles = 0;
+
+struct weapontexcell
+{
+	float x;
+	float y;
+	float w;
+	float h;
+};
+struct renderparams
+{
+	float sizex;
+	float sizey;
+	float offsetx;
+	float offsety;
+};
+int numcellsx = 32;
+int numcellsy = 32;
+renderparams weaponrenderparams[WEAPON_NUMWEAPONS];
+renderparams modifierrenderparams[WEAPON_NUMWEAPONS];
+
+weapontexcell weaponprojtexcoord[WEAPON_NUMWEAPONS];
+weapontexcell weapontexcoord[WEAPON_NUMWEAPONS];
+weapontexcell weapontexcoordcursor[WEAPON_NUMWEAPONS];
+
+weapontexcell poweruptexcoord[POWERUP_TYPE_NUMPOWERUPS];
+
+weapontexcell modifiertexcoord[MODIFIER_NUMMODIFIERS];
+weapontexcell modifiertexcoordcursor[MODIFIER_NUMMODIFIERS];
+
+int nummuzzletex[WEAPON_NUMWEAPONS];
+weapontexcell muzzletexcoord[WEAPON_NUMWEAPONS][3];
+renderparams muzzleparams[WEAPON_NUMWEAPONS];
+
+#define NUMHADOKENS 6
+#define NUMSTARS 2
+#define NUMPARTICLES 9
+int particlesnumcellsx = 16;
+int particlesnumcellsy = 16;
+weapontexcell chaintexcoord;
+weapontexcell chainheadtexcoord;
+weapontexcell stars[NUMSTARS];
+
+float lifemodifier[NUMPARTICLES];
+vec4 particlecolors[NUMPARTICLES];
+weapontexcell particlestexcoord[NUMPARTICLES];
+
+int charnumcellsx = 8;
+int charnumcellsy = 32;
+int charoffsety = 2;
+weapontexcell body[2];
+weapontexcell leye;
+weapontexcell reye;
+weapontexcell feet[2];
+
+int charids[16] = { 2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3 };
+
+renderparams hadokenparams[6];
+weapontexcell hadoken[6];
+
+float recoils[WEAPON_NUMWEAPONS] = { 10.0f, 10.0f, 10.0f, 10.0f };
+
+static int font_texture = 0;
+static vec2 mouse_pos;
+
+
+static vec2 local_player_pos;
+static obj_player *local_player;
+
+float frandom()
+{
+	return rand()/(float)(RAND_MAX);
+}
+
+float sign(float f)
+{
+	return f<0.0f?-1.0f:1.0f;
+}
+
+// sound helpers
+template<int N>
+class sound_kit
+{
+private:
+	int sounds[N];
+	int last_id;
+public:
+	sound_kit() : last_id(-1) { }
+	
+	int& operator[](int id) { return sounds[id]; }
+
+	inline void play_random(float vol = 1.0f, float pan = 0.0f);
+};
+
+template<>
+inline void sound_kit<1>::play_random(float vol, float pan)
+{
+	snd_play(sounds[0], SND_PLAY_ONCE, vol, pan);
+}
+
+template<int N>
+inline void sound_kit<N>::play_random(float vol, float pan)
+{
+	int id;
+	do {
+		id = rand() % N;
+	} while(id == last_id);
+	snd_play(sounds[id], SND_PLAY_ONCE, vol, pan);
+	last_id = id;
+}
+
+
+// sound volume tweak
+static const float stereo_separation = 0.01f;
+static const float stereo_separation_deadzone = 512.0f;
+static const float volume_distance_falloff = 100.0f;
+static const float volume_distance_deadzone = 512.0f;
+static const float volume_gun = 0.5f;
+static const float volume_tee = 0.5f;
+static const float volume_hit = 0.5f;
+static const float volume_music = 0.8f;
+
+// sounds
+sound_kit<3> sound_gun_fire;
+sound_kit<3> sound_shotty_fire;
+sound_kit<3> sound_flump_launch;
+sound_kit<3> sound_hammer_swing;
+sound_kit<3> sound_ninja_attack;
+
+sound_kit<3> sound_flump_explode;
+sound_kit<4> sound_ninja_hit;
+
+sound_kit<3> sound_weapon_switch;
+
+sound_kit<12> sound_pain_short;
+sound_kit<2> sound_pain_long;
+
+sound_kit<4> sound_body_jump;
+sound_kit<4> sound_body_land;
+sound_kit<2> sound_body_splat;
+
+sound_kit<7> sound_spawn;
+sound_kit<2> sound_tee_cry;
+
+sound_kit<1> sound_hook_loop;
+sound_kit<3> sound_hook_attach;
+
+void sound_vol_pan(const vec2& p, float *vol, float *pan)
+{
+	vec2 player_to_ev = p - local_player_pos;
+	*pan = 0.0f;
+	*vol = 1.0f;
+
+	if(abs(player_to_ev.x) > stereo_separation_deadzone)
+	{
+		*pan = stereo_separation * (player_to_ev.x - sign(player_to_ev.x)*stereo_separation_deadzone);
+		if(*pan < -1.0f) *pan = -1.0f;
+		if(*pan > 1.0f) *pan = 1.0f;
+	}
+
+	float len = length(player_to_ev);
+	if(len > volume_distance_deadzone)
+	{
+		*vol = volume_distance_falloff / (len - volume_distance_deadzone);
+
+		if(*vol < 0.0f) *vol = 0.0f;
+		if(*vol > 1.0f) *vol = 1.0f;
+	}
+}
+
+// TODO: we should do something nicer then this
+static void cell_select_ex(int cx, int cy, float x, float y, float w, float h)
+{
+	gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy);
+}
+
+static void cell_select_ex_flip_x(int cx, int cy, float x, float y, float w, float h)
+{
+	gfx_quads_setsubset((x+w)/(float)cx,y/(float)cy,x /(float)cx,(y+h)/(float)cy);
+}
+
+static void cell_select_ex_flip_y(int cx, int cy, float x, float y, float w, float h)
+{
+	gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy);
+}
+
+static void cell_select(int x, int y, int w, int h)
+{
+	gfx_quads_setsubset(x/16.0f,y/16.0f,(x+w)/16.0f,(y+h)/16.0f);
+}
+
+inline void cell_select_flip_x(int x, int y, int w, int h)
+{
+	gfx_quads_setsubset((x+w)/16.0f,y/16.0f,(x)/16.0f,(y+h)/16.0f);
+}
+
+inline void cell_select_flip_y(int x, int y, int w, int h)
+{
+	gfx_quads_setsubset(x/16.0f,(y+h)/16.0f,(x+w)/16.0f,(y)/16.0f);
+}
+
+struct particle
+{
+	vec2 pos;
+	vec2 vel;
+	float life;
+	float max_life;
+	float size;
+	
+	float rot;
+	float rotspeed;
+	
+	float gravity;
+	float friction;
+	int iparticle;
+	
+	vec4 color;
+};
+
+void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity)
+{
+	vec2 pos = *inout_pos;
+	vec2 vel = *inout_vel;
+	if(col_check_point(pos + vel))
+	{
+		int affected = 0;
+		if(col_check_point(pos.x + vel.x, pos.y))
+		{
+			inout_vel->x *= -elasticity;
+			affected++;
+		}
+
+		if(col_check_point(pos.x, pos.y + vel.y))
+		{
+			inout_vel->y *= -elasticity;
+			affected++;
+		}
+		
+		if(affected == 0)
+		{
+			inout_vel->x *= -elasticity;
+			inout_vel->y *= -elasticity;
+		}
+	}
+	else
+	{
+		*inout_pos = pos + vel;
+	}
+}
+
+class health_texts
+{
+public:
+	int64 lastupdate;
+	struct item
+	{
+		vec2 pos;
+		vec2 vel;
+		int amount;
+		int istar;
+		float life;
+		float startangle;
+	};
+	
+	enum
+	{
+		MAX_ITEMS=16,
+	};
+
+	health_texts()
+	{
+		lastupdate = 0;
+	}
+	
+	item items[MAX_ITEMS];
+	int num_items;
+	
+	item *create_i()
+	{
+		if (num_items < MAX_ITEMS)
+		{
+			item *p = &items[num_items];
+			num_items++;
+			return p;
+		}
+		return 0;
+	}
+	
+	void destroy_i(item *i)
+	{
+		num_items--;
+		*i = items[num_items];
+	}
+	
+	void create(vec2 pos, int amount)
+	{
+		amount = max(1,amount);
+		for (int j = 0; j < amount; j++)
+		{
+			float a = j/(float)amount-0.5f;
+			item *i = create_i();
+			if (i)
+			{
+				i->pos = pos;
+				i->pos.y -= 20.0f;
+				i->pos.x += ((float)rand()/(float)RAND_MAX) * 5.0f;
+				i->amount = amount;
+				i->life = 1.5f;
+				i->istar = rand() % NUMSTARS;
+				i->vel = vec2(((float)rand()/(float)RAND_MAX) * 50.0f,-150.0f);
+				i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi;
+			}
+		}
+	}
+	
+	void render()
+	{
+		if (!lastupdate)
+			lastupdate = time_get();
+
+		int64 lasttime = lastupdate;
+		lastupdate = time_get();
+
+		float delta = (float) (lastupdate - lasttime) / (float)time_freq();
+		gfx_texture_set(texture_particles);
+		gfx_quads_begin();
+		for(int i = 0; i < num_items;)
+		{
+			items[i].vel += vec2(0,500.0f) * delta;
+			items[i].pos += items[i].vel * delta;
+			items[i].life -= delta;
+			//items[i].pos.y -= frametime*15.0f;
+			if(items[i].life < 0.0f)
+				destroy_i(&items[i]);
+			else
+			{
+				gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life / 1.5f);
+				gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f);
+				float size = 64.0f;
+				cell_select_ex(particlesnumcellsx,particlesnumcellsy, stars[items[i].istar].x,stars[items[i].istar].y, stars[items[i].istar].w, stars[items[i].istar].h);
+				gfx_quads_draw(items[i].pos.x-size/2, items[i].pos.y-size/2, size, size);
+				/*char buf[32];
+				if(items[i].amount < 0)
+				{
+					sprintf(buf, "%d", items[i].amount*-1);
+				}
+				else
+				{
+					sprintf(buf, "%d", items[i].amount);
+				}
+				float size = 42.0f;
+				if(items[i].life > 1.25f)
+					size += 42.0f * ((items[i].life - 1.25f) * 4);
+				gfx_quads_text(items[i].pos.x-size/2, items[i].pos.y, size, buf);*/
+				i++;
+			}
+		}
+		gfx_quads_end();
+	}
+	
+};
+
+/*class texture_animator
+{
+public:
+	
+	int texture;
+	int numframes;
+	float duration;
+	float* framereltime;
+	weapontexcell* params;
+	texture_animator()
+	{
+		texture = -1;
+		numframes = 0;
+		duration = 0;
+		framereltime = 0;
+		params = 0;
+	}
+
+	~texture_animator()
+	{
+		if (params)
+			mem_free(params);
+		if (framereltime)
+			mem_free(framereltime);
+	}
+
+	void create_anim(int texture, int numframes, float duration)
+	{
+		framereltime = 0;
+		params = 0;
+		this->texture = texture;
+		this->numframes = numframes;
+		this->duration = duration;
+		if (numframes)
+		{
+			framereltime = (float*)mem_alloc(sizeof(float) * numframes,1);
+			params = (weapontexcell*)mem_alloc(sizeof(renderparams) * numframes,1);
+			float delta = 1.0f / (float)(numframes - 1);
+			for (int i = 0; i < numframes; i++)
+			{
+				framereltime[i] = delta * i;
+			}
+		}	
+	}
+
+	static void create_gunmuzzle(texture_animator& anim, int texture, float duration)
+	{
+		anim.create_anim(texture, 3, duration);
+		anim.params[0].x = 8;
+		anim.params[0].y = 4;
+		anim.params[0].w = 3;
+		anim.params[0].h = 2;
+		anim.params[1].x = 12;
+		anim.params[1].y = 4;
+		anim.params[1].w = 3;
+		anim.params[1].h = 2;
+		anim.params[2].x = 16;
+		anim.params[2].y = 4;
+		anim.params[2].w = 3;
+		anim.params[2].h = 2;
+	}
+
+	static void create_shotgunmuzzle()
+	{
+
+	}
+};*/
+
+class keyframe
+{
+public:
+	vec2 pos;
+	float angle;
+	float relativetime;
+};
+
+class anim
+{
+public:
+	keyframe* keyframes;
+	int numframes;
+	float duration;
+	anim()
+	{
+		numframes = 0;
+		keyframes = 0;
+	}
+	~anim()
+	{
+		if (keyframes)
+			mem_free(keyframes);
+	}
+
+	void create_anim(int numframes, float duration)
+	{
+		if (keyframes)
+			mem_free(keyframes);
+
+		this->numframes = numframes;
+		this->duration = duration;
+		keyframes = (keyframe*)mem_alloc(sizeof(keyframe) * numframes,1);
+		float delta = 1.0f / (float) (numframes - 1);
+		for (int i = 0; i < numframes; i++)
+		{
+			keyframes[i].pos = vec2(0.0f,0.0f);
+			keyframes[i].angle = 0;
+			keyframes[i].relativetime = delta * (float)i;
+		}
+	}
+
+	void getframes(float relativetime, keyframe*& frame1, keyframe*& frame2, float& blend)
+	{
+		for (int i = 1; i < numframes; i++)
+		{
+			if (keyframes[i-1].relativetime <= relativetime && keyframes[i].relativetime >= relativetime)
+			{
+				frame1 = &keyframes[i-1];
+				frame2 = &keyframes[i];
+				blend = (relativetime - frame1->relativetime) / (frame2->relativetime - frame1->relativetime);
+			}
+		}
+	}
+
+	void evalanim(float time, vec2& pos, float& angle)
+	{
+		float reltime = max(0.0f, min(1.0f, time / duration));
+		keyframe* frame1 = 0;
+		keyframe* frame2 = 0;
+		float blend = 0.0f;
+		getframes(reltime, frame1, frame2, blend);
+
+		if (frame1 && frame2)
+		{
+			pos = mix(frame1->pos, frame2->pos, blend);
+			angle = LERP(frame1->angle, frame2->angle, blend);
+		}
+	}
+
+	static void setup_hammer(anim& hammeranim)
+	{
+		// straight up = -0.25
+		// frame0 = standard pose		time 0
+		// frame1 = back a little		time 0.3
+		// frame2 = over head			time 0.4
+		// frame3 = on ground smashed	time 0.5
+		// frame4 = back to standard pose time 1.0
+		hammeranim.create_anim(5, 1.0f);
+		// only angles... (for now...)
+		hammeranim.keyframes[0].angle = -0.35f * pi * 2.0f;
+		hammeranim.keyframes[1].angle = -0.4f * pi * 2.0f;
+		hammeranim.keyframes[1].relativetime = 0.3f;
+		hammeranim.keyframes[2].angle = -0.25f;
+		hammeranim.keyframes[2].relativetime = 0.4f;
+		hammeranim.keyframes[3].angle = 0.0f * pi * 2.0f;
+		hammeranim.keyframes[3].relativetime = 0.5f;
+		hammeranim.keyframes[4].angle = -0.35f * pi * 2.0f;
+		hammeranim.keyframes[4].relativetime = 1.0f;
+	}
+
+	static void setup_ninja(anim& ninjanim)
+	{
+		// (straight up = -0.25)
+		// frame0 = standard pose straight back		time 0.0
+		// frame1 = overhead attack frame 1			time 0.1
+		// frame2 = attack end frame				time 0.15
+		// frame3 = attack hold frame (a bit up)	time 0.4
+		// frame4 = attack hold frame end			time 0.7
+		// frame5 = endframe						time 1.0
+		ninjanim.create_anim(6, 1.0f);
+		// only angles... (for now...)
+		ninjanim.keyframes[0].angle = -0.5f * pi * 2.0f;
+
+		ninjanim.keyframes[1].angle = -0.3f * pi * 2.0f;
+		ninjanim.keyframes[1].relativetime = 0.1f;
+
+		ninjanim.keyframes[2].angle = 0.1f * pi * 2.0f;
+		ninjanim.keyframes[2].relativetime = 0.15f;
+
+		ninjanim.keyframes[3].angle = -0.05f * pi * 2.0f;
+		ninjanim.keyframes[3].relativetime = 0.42;
+
+		ninjanim.keyframes[4].angle = -0.05f * pi * 2.0f;
+		ninjanim.keyframes[4].relativetime = 0.5f;
+
+		ninjanim.keyframes[5].angle = -0.5f * pi * 2.0f;
+		ninjanim.keyframes[5].relativetime = 1.0f;
+	}
+};
+
+static anim hammeranim;
+static anim ninjaanim;
+static health_texts healthmods;
+
+class particle_system
+{
+public:
+	enum
+	{
+		MAX_PARTICLES=1024,
+	};
+	
+	particle particles[MAX_PARTICLES];
+	int num_particles;
+	
+	particle_system()
+	{
+		num_particles = 0;
+	}
+	
+	void new_particle(vec2 pos, vec2 vel, float life, float size, float gravity, float friction)
+	{
+		if (num_particles >= MAX_PARTICLES)
+			return;
+
+		particles[num_particles].iparticle = rand() % NUMPARTICLES;
+		particles[num_particles].pos = pos;
+		particles[num_particles].vel = vel;
+		particles[num_particles].life = life - lifemodifier[particles[num_particles].iparticle] * life;
+		particles[num_particles].size = size;
+		particles[num_particles].max_life = life;
+		particles[num_particles].gravity = gravity;
+		particles[num_particles].friction = friction;
+		particles[num_particles].rot = frandom()*pi*2;
+		particles[num_particles].rotspeed = frandom() * 10.0f;
+		num_particles++;
+	}
+	
+	void update(float time_passed)
+	{
+		for(int i = 0; i < num_particles; i++)
+		{
+			particles[i].vel.y += particles[i].gravity*time_passed;
+			particles[i].vel *= particles[i].friction;
+			vec2 vel = particles[i].vel*time_passed;
+			move_point(&particles[i].pos, &vel, 0.1f+0.9f*frandom());
+			particles[i].vel = vel* (1.0f/time_passed);
+			particles[i].life += time_passed;
+			particles[i].rot += time_passed * particles[i].rotspeed;
+			
+			// check particle death
+			if(particles[i].life > particles[i].max_life)
+			{
+				num_particles--;
+				particles[i] = particles[num_particles];
+				i--;
+			}
+		}
+	}
+	
+	void render()
+	{
+		gfx_blend_additive();
+		gfx_texture_set(texture_particles);
+		gfx_quads_begin();
+		//cell_select(4,1,1,1);
+		//cell_select(0,6,2,2);
+		//gfx_quads_setrotation(get_angle(vec2(proj->vx, proj->vy)));
+		for(int i = 0; i < num_particles; i++)
+		{
+			int type = particles[i].iparticle;
+			cell_select_ex(particlesnumcellsx,particlesnumcellsy,particlestexcoord[type].x, particlestexcoord[type].y, particlestexcoord[type].w, particlestexcoord[type].h);
+			float a = 1 - particles[i].life / particles[i].max_life;
+			vec2 p = particles[i].pos;
+			//a *= length(particles[i].vel) * 0.01f;
+			gfx_quads_setrotation(particles[i].rot);
+			gfx_quads_setcolor(particlecolors[type].x,particlecolors[type].y,particlecolors[type].z,pow(a,0.75f));
+			//gfx_quads_setcolor(particlecolors[type].x * 0.5,particlecolors[type].y * 0.5,particlecolors[type].z* 0.5,pow(a,0.75f));
+			//gfx_quads_setcolor(particlecolors[type].x * 0.0,particlecolors[type].y * 0.0,particlecolors[type].z* 0.0,pow(a,0.75f));
+			//gfx_quads_setcolor(0.64f*2,0.28f*2,0.16f*2,pow(a,0.75f));
+			gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size);
+		}
+		gfx_quads_end();		
+		gfx_blend_normal();
+	}
+};
+
+static particle_system temp_system;
+ 
+void modc_init()
+{
+	// load textures
+	texture_weapon = gfx_load_texture_tga("data/tileset_weapons.tga");
+	texture_game = gfx_load_texture_tga("data/game_main.tga");
+	texture_char_default = gfx_load_texture_tga("data/char_teefault.tga");
+	texture_sun = gfx_load_texture_tga("data/sun.tga");
+	texture_particles = gfx_load_texture_tga("data/tileset_particles.tga");
+	font_texture = gfx_load_texture_tga("data/debug_font.tga");
+
+	
+	// load sounds
+	sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav");
+	sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav");
+	sound_gun_fire[1] = snd_load_wav("data/audio/wp_gun_fire-02.wav");
+	sound_shotty_fire[0] = snd_load_wav("data/audio/wp_shotty_fire-01.wav");
+	sound_shotty_fire[1] = snd_load_wav("data/audio/wp_shotty_fire-02.wav");
+	sound_shotty_fire[2] = snd_load_wav("data/audio/wp_shotty_fire-03.wav");
+	sound_flump_launch[0] = snd_load_wav("data/audio/wp_flump_launch-01.wav");
+	sound_flump_launch[1] = snd_load_wav("data/audio/wp_flump_launch-02.wav");
+	sound_flump_launch[2] = snd_load_wav("data/audio/wp_flump_launch-03.wav");
+	sound_hammer_swing[0] = snd_load_wav("data/audio/wp_hammer_swing-01.wav");
+	sound_hammer_swing[1] = snd_load_wav("data/audio/wp_hammer_swing-02.wav");
+	sound_hammer_swing[2] = snd_load_wav("data/audio/wp_hammer_swing-03.wav");
+	sound_ninja_attack[0] = snd_load_wav("data/audio/wp_ninja_attack-01.wav");
+	sound_ninja_attack[1] = snd_load_wav("data/audio/wp_ninja_attack-02.wav");
+	sound_ninja_attack[2] = snd_load_wav("data/audio/wp_ninja_attack-03.wav");
+
+	sound_flump_explode[0] = snd_load_wav("data/audio/wp_flump_explo-01.wav");
+	sound_flump_explode[1] = snd_load_wav("data/audio/wp_flump_explo-02.wav");
+	sound_flump_explode[2] = snd_load_wav("data/audio/wp_flump_explo-03.wav");
+	sound_ninja_hit[0] = snd_load_wav("data/audio/wp_ninja_hit-01.wav");
+	sound_ninja_hit[1] = snd_load_wav("data/audio/wp_ninja_hit-02.wav");
+	sound_ninja_hit[2] = snd_load_wav("data/audio/wp_ninja_hit-03.wav");
+	sound_ninja_hit[3] = snd_load_wav("data/audio/wp_ninja_hit-04.wav");
+
+	sound_weapon_switch[0] = snd_load_wav("data/audio/wp_switch-01.wav");
+	sound_weapon_switch[1] = snd_load_wav("data/audio/wp_switch-02.wav");
+	sound_weapon_switch[2] = snd_load_wav("data/audio/wp_switch-03.wav");
+
+	sound_pain_short[0] = snd_load_wav("data/audio/vo_teefault_pain_short-01.wav");
+	sound_pain_short[1] = snd_load_wav("data/audio/vo_teefault_pain_short-02.wav");
+	sound_pain_short[2] = snd_load_wav("data/audio/vo_teefault_pain_short-03.wav");
+	sound_pain_short[3] = snd_load_wav("data/audio/vo_teefault_pain_short-04.wav");
+	sound_pain_short[4] = snd_load_wav("data/audio/vo_teefault_pain_short-05.wav");
+	sound_pain_short[5] = snd_load_wav("data/audio/vo_teefault_pain_short-06.wav");
+	sound_pain_short[6] = snd_load_wav("data/audio/vo_teefault_pain_short-07.wav");
+	sound_pain_short[7] = snd_load_wav("data/audio/vo_teefault_pain_short-08.wav");
+	sound_pain_short[8] = snd_load_wav("data/audio/vo_teefault_pain_short-09.wav");
+	sound_pain_short[9] = snd_load_wav("data/audio/vo_teefault_pain_short-10.wav");
+	sound_pain_short[10] = snd_load_wav("data/audio/vo_teefault_pain_short-11.wav");
+	sound_pain_short[11] = snd_load_wav("data/audio/vo_teefault_pain_short-12.wav");
+
+	sound_pain_long[0] = snd_load_wav("data/audio/vo_teefault_pain_long-01.wav");
+	sound_pain_long[1] = snd_load_wav("data/audio/vo_teefault_pain_long-02.wav");
+
+	sound_body_land[0] = snd_load_wav("data/audio/foley_land-01.wav");
+	sound_body_land[1] = snd_load_wav("data/audio/foley_land-02.wav");
+	sound_body_land[2] = snd_load_wav("data/audio/foley_land-03.wav");
+	sound_body_land[3] = snd_load_wav("data/audio/foley_land-04.wav");
+	sound_body_jump[0] = snd_load_wav("data/audio/foley_foot_left-01.wav");
+	sound_body_jump[1] = snd_load_wav("data/audio/foley_foot_left-02.wav");
+	sound_body_jump[2] = snd_load_wav("data/audio/foley_foot_left-03.wav");
+	sound_body_jump[3] = snd_load_wav("data/audio/foley_foot_left-04.wav");
+	sound_body_jump[4] = snd_load_wav("data/audio/foley_foot_right-01.wav");
+	sound_body_jump[5] = snd_load_wav("data/audio/foley_foot_right-02.wav");
+	sound_body_jump[6] = snd_load_wav("data/audio/foley_foot_right-03.wav");
+	sound_body_jump[7] = snd_load_wav("data/audio/foley_foot_right-04.wav");
+
+	sound_body_splat[1] = snd_load_wav("data/audio/foley_body_splat-02.wav");
+	sound_body_splat[2] = snd_load_wav("data/audio/foley_body_splat-03.wav");
+	sound_body_splat[3] = snd_load_wav("data/audio/foley_body_splat-04.wav");
+
+	sound_spawn[0] = snd_load_wav("data/audio/vo_teefault_spawn-01.wav");
+	sound_spawn[1] = snd_load_wav("data/audio/vo_teefault_spawn-02.wav");
+	sound_spawn[2] = snd_load_wav("data/audio/vo_teefault_spawn-03.wav");
+	sound_spawn[3] = snd_load_wav("data/audio/vo_teefault_spawn-04.wav");
+	sound_spawn[4] = snd_load_wav("data/audio/vo_teefault_spawn-05.wav");
+	sound_spawn[5] = snd_load_wav("data/audio/vo_teefault_spawn-06.wav");
+	sound_spawn[6] = snd_load_wav("data/audio/vo_teefault_spawn-07.wav");
+
+	sound_tee_cry[0] = snd_load_wav("data/audio/vo_teefault_cry-01.wav");
+	sound_tee_cry[1] = snd_load_wav("data/audio/vo_teefault_cry-02.wav");
+
+	//sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-01.wav");
+	sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-02.wav");
+	sound_hook_attach[0] = snd_load_wav("data/audio/hook_attach-01.wav");
+	sound_hook_attach[1] = snd_load_wav("data/audio/hook_attach-02.wav");
+	sound_hook_attach[2] = snd_load_wav("data/audio/hook_attach-03.wav");
+
+	poweruptexcoord[POWERUP_TYPE_HEALTH].x = 10;
+	poweruptexcoord[POWERUP_TYPE_HEALTH].y = 2;
+	poweruptexcoord[POWERUP_TYPE_HEALTH].w = 2;
+	poweruptexcoord[POWERUP_TYPE_HEALTH].h = 2;
+
+	poweruptexcoord[POWERUP_TYPE_ARMOR].x = 12;
+	poweruptexcoord[POWERUP_TYPE_ARMOR].y = 2;
+	poweruptexcoord[POWERUP_TYPE_ARMOR].w = 2;
+	poweruptexcoord[POWERUP_TYPE_ARMOR].h = 2;
+	
+	poweruptexcoord[POWERUP_TYPE_WEAPON].x = 3;
+	poweruptexcoord[POWERUP_TYPE_WEAPON].y = 0;
+	poweruptexcoord[POWERUP_TYPE_WEAPON].w = 6;
+	poweruptexcoord[POWERUP_TYPE_WEAPON].h = 2;
+
+	poweruptexcoord[POWERUP_TYPE_NINJA].x = 3;
+	poweruptexcoord[POWERUP_TYPE_NINJA].y = 10;
+	poweruptexcoord[POWERUP_TYPE_NINJA].w = 7;
+	poweruptexcoord[POWERUP_TYPE_NINJA].h = 2;
+
+	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].x = 3;
+	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].y = 0;
+	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].w = 6;
+	poweruptexcoord[POWERUP_TYPE_TIMEFIELD].h = 2;
+
+	// Setup weapon cell coords
+	float sizemodifier = 1.0f;
+	weaponrenderparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_GUN].sizey = 30.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_GUN].offsetx = 32.0f;
+	weaponrenderparams[WEAPON_TYPE_GUN].offsety = 4.0f;
+	weapontexcoordcursor[WEAPON_TYPE_GUN].x = 0;
+	weapontexcoordcursor[WEAPON_TYPE_GUN].y = 4;
+	weapontexcoordcursor[WEAPON_TYPE_GUN].w = 2;
+	weapontexcoordcursor[WEAPON_TYPE_GUN].h = 2;
+	weapontexcoord[WEAPON_TYPE_GUN].x = 2;
+	weapontexcoord[WEAPON_TYPE_GUN].y = 4;
+	weapontexcoord[WEAPON_TYPE_GUN].w = 4;
+	weapontexcoord[WEAPON_TYPE_GUN].h = 2;
+	weaponprojtexcoord[WEAPON_TYPE_GUN].x = 6;
+	weaponprojtexcoord[WEAPON_TYPE_GUN].y = 4;
+	weaponprojtexcoord[WEAPON_TYPE_GUN].w = 2;
+	weaponprojtexcoord[WEAPON_TYPE_GUN].h = 2;
+
+	nummuzzletex[WEAPON_TYPE_GUN] = 3;
+	muzzletexcoord[WEAPON_TYPE_GUN][0].x = 8;
+	muzzletexcoord[WEAPON_TYPE_GUN][0].y = 4;
+	muzzletexcoord[WEAPON_TYPE_GUN][0].w = 3;
+	muzzletexcoord[WEAPON_TYPE_GUN][0].h = 2;
+	muzzletexcoord[WEAPON_TYPE_GUN][1].x = 12;
+	muzzletexcoord[WEAPON_TYPE_GUN][1].y = 4;
+	muzzletexcoord[WEAPON_TYPE_GUN][1].w = 3;
+	muzzletexcoord[WEAPON_TYPE_GUN][1].h = 2;
+	muzzletexcoord[WEAPON_TYPE_GUN][2].x = 16;
+	muzzletexcoord[WEAPON_TYPE_GUN][2].y = 4;
+	muzzletexcoord[WEAPON_TYPE_GUN][2].w = 3;
+	muzzletexcoord[WEAPON_TYPE_GUN][2].h = 2;
+
+	muzzleparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_GUN].sizey = 40.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_GUN].offsetx = 50.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_GUN].offsety = 6.0f * sizemodifier;
+
+	sizemodifier = 1.3f;
+	weaponrenderparams[WEAPON_TYPE_ROCKET].sizex = 70.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_ROCKET].sizey = 20.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_ROCKET].offsetx = 24.0f;
+	weaponrenderparams[WEAPON_TYPE_ROCKET].offsety = -2.0f;
+	weapontexcoordcursor[WEAPON_TYPE_ROCKET].x = 0;
+	weapontexcoordcursor[WEAPON_TYPE_ROCKET].y = 8;
+	weapontexcoordcursor[WEAPON_TYPE_ROCKET].w = 2;
+	weapontexcoordcursor[WEAPON_TYPE_ROCKET].h = 2;
+	weapontexcoord[WEAPON_TYPE_ROCKET].x = 2;
+	weapontexcoord[WEAPON_TYPE_ROCKET].y = 8;
+	weapontexcoord[WEAPON_TYPE_ROCKET].w = 7;
+	weapontexcoord[WEAPON_TYPE_ROCKET].h = 2;
+	weaponprojtexcoord[WEAPON_TYPE_ROCKET].x = 10;
+	weaponprojtexcoord[WEAPON_TYPE_ROCKET].y = 8;
+	weaponprojtexcoord[WEAPON_TYPE_ROCKET].w = 2;
+	weaponprojtexcoord[WEAPON_TYPE_ROCKET].h = 2;
+
+	/*weaponrenderparams[WEAPON_TYPE_SNIPER].sizex = 60.0f;
+	weaponrenderparams[WEAPON_TYPE_SNIPER].sizey = 20.0f;
+	weaponrenderparams[WEAPON_TYPE_SNIPER].offsetx = 16.0f;
+	weaponrenderparams[WEAPON_TYPE_SNIPER].offsety = 4.0f;
+	weapontexcoordcursor[WEAPON_TYPE_SNIPER].x = 0;
+	weapontexcoordcursor[WEAPON_TYPE_SNIPER].y = 6;
+	weapontexcoordcursor[WEAPON_TYPE_SNIPER].w = 2;
+	weapontexcoordcursor[WEAPON_TYPE_SNIPER].h = 2;
+	weapontexcoord[WEAPON_TYPE_SNIPER].x = 3;
+	weapontexcoord[WEAPON_TYPE_SNIPER].y = 6;
+	weapontexcoord[WEAPON_TYPE_SNIPER].w = 6;
+	weapontexcoord[WEAPON_TYPE_SNIPER].h = 2;
+	weaponprojtexcoord[WEAPON_TYPE_SNIPER].x = 10;
+	weaponprojtexcoord[WEAPON_TYPE_SNIPER].y = 6;
+	weaponprojtexcoord[WEAPON_TYPE_SNIPER].w = 1;
+	weaponprojtexcoord[WEAPON_TYPE_SNIPER].h = 1;*/
+
+	weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizex = 80.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizey = 20.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsetx = 24.0f;
+	weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsety = -2.0f;
+	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].x = 0;
+	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].y = 6;
+	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].w = 2;
+	weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].h = 2;
+	weapontexcoord[WEAPON_TYPE_SHOTGUN].x = 2;
+	weapontexcoord[WEAPON_TYPE_SHOTGUN].y = 6;
+	weapontexcoord[WEAPON_TYPE_SHOTGUN].w = 8;
+	weapontexcoord[WEAPON_TYPE_SHOTGUN].h = 2;
+	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].x = 10;
+	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].y = 6;
+	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].w = 2;
+	weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].h = 2;
+
+	nummuzzletex[WEAPON_TYPE_SHOTGUN] = 3;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].x = 12;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].y = 6;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].w = 3;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].h = 2;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].x = 16;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].y = 6;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].w = 3;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].h = 2;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].x = 20;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].y = 6;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].w = 3;
+	muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].h = 2;
+
+	muzzleparams[WEAPON_TYPE_SHOTGUN].sizex = 60.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_SHOTGUN].sizey = 40.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_SHOTGUN].offsetx = 50.0f * sizemodifier;
+	muzzleparams[WEAPON_TYPE_SHOTGUN].offsety = 6.0f * sizemodifier;
+
+
+
+	weaponrenderparams[WEAPON_TYPE_MELEE].sizex = 60.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_MELEE].sizey = 50.0f * sizemodifier;
+	weaponrenderparams[WEAPON_TYPE_MELEE].offsetx = 20.0f;
+	weaponrenderparams[WEAPON_TYPE_MELEE].offsety = -4.0f;
+	weapontexcoordcursor[WEAPON_TYPE_MELEE].x = 0;
+	weapontexcoordcursor[WEAPON_TYPE_MELEE].y = 0;
+	weapontexcoordcursor[WEAPON_TYPE_MELEE].w = 2;
+	weapontexcoordcursor[WEAPON_TYPE_MELEE].h = 2;
+	weapontexcoord[WEAPON_TYPE_MELEE].x = 2;
+	weapontexcoord[WEAPON_TYPE_MELEE].y = 1;
+	weapontexcoord[WEAPON_TYPE_MELEE].w = 4;
+	weapontexcoord[WEAPON_TYPE_MELEE].h = 3;
+	weaponprojtexcoord[WEAPON_TYPE_MELEE].x = 0;
+	weaponprojtexcoord[WEAPON_TYPE_MELEE].y = 0;
+	weaponprojtexcoord[WEAPON_TYPE_MELEE].w = 0;
+	weaponprojtexcoord[WEAPON_TYPE_MELEE].h = 0;
+
+
+	// MODIFIERS
+	sizemodifier = 2.0;
+	modifierrenderparams[MODIFIER_TYPE_NINJA].sizex = 60.0f * sizemodifier;
+	modifierrenderparams[MODIFIER_TYPE_NINJA].sizey = 20.0f * sizemodifier;
+	modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx = 20.0f;
+	modifierrenderparams[MODIFIER_TYPE_NINJA].offsety = 4.0f;
+	modifiertexcoord[MODIFIER_TYPE_NINJA].x = 2;
+	modifiertexcoord[MODIFIER_TYPE_NINJA].y = 10;
+	modifiertexcoord[MODIFIER_TYPE_NINJA].w = 7;
+	modifiertexcoord[MODIFIER_TYPE_NINJA].h = 2;
+	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x = 0;
+	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y = 10;
+	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w = 2;
+	modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h = 2;
+
+	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizex = 60.0f * sizemodifier;
+	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizey = 20.0f * sizemodifier;
+	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsetx = 16.0f;
+	modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsety = 4.0f;
+	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].x = 0;
+	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].y = 0;
+	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].w = 0;
+	modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].h = 0;
+
+	stars[0].x = 0;
+	stars[0].y = 0;
+	stars[0].w = 2;
+	stars[0].h = 2;
+
+	stars[1].x = 0;
+	stars[1].y = 2;
+	stars[1].w = 2;
+	stars[1].h = 2;
+
+	particlecolors[0].x = 0.7f;
+	particlecolors[0].y = 0.7f;
+	particlecolors[0].z = 0.7f;
+	particlecolors[0].w = 1.0f;
+	particlestexcoord[0].x = 2;
+	particlestexcoord[0].y = 0;
+	particlestexcoord[0].w = 2;
+	particlestexcoord[0].h = 2;
+	particlecolors[1].x = 1.0f;
+	particlecolors[1].y = 1.0f;
+	particlecolors[1].z = 1.0f; 
+	particlecolors[1].w = 1.0f;
+	particlestexcoord[1].x = 4;
+	particlestexcoord[1].y = 0;
+	particlestexcoord[1].w = 2;
+	particlestexcoord[1].h = 2;
+	particlecolors[2].x = 0.8f;
+	particlecolors[2].y = 0.8f;
+	particlecolors[2].z = 0.8f;
+	particlecolors[2].w = 1.0f;
+	particlestexcoord[2].x = 6;
+	particlestexcoord[2].y = 0;
+	particlestexcoord[2].w = 2;
+	particlestexcoord[2].h = 2;
+	particlecolors[3].x = 0.988f;
+	particlecolors[3].y = 1.0f;
+	particlecolors[3].z = 0.16f;
+	particlecolors[3].w = 1.0f;
+	particlestexcoord[3].x = 8;
+	particlestexcoord[3].y = 0;
+	particlestexcoord[3].w = 2;
+	particlestexcoord[3].h = 2;
+	particlecolors[4].x = 1.0f;
+	particlecolors[4].y = 1.0f;
+	particlecolors[4].z = 1.0f;
+	particlecolors[4].w = 1.0f;
+	particlestexcoord[4].x = 10;
+	particlestexcoord[4].y = 0;
+	particlestexcoord[4].w = 2;
+	particlestexcoord[4].h = 2;
+	particlecolors[5].x = 0.6f;
+	particlecolors[5].y = 0.6f;
+	particlecolors[5].z = 0.6f;
+	particlecolors[5].w = 1.0f;
+	particlestexcoord[5].x = 2;
+	particlestexcoord[5].y = 2;
+	particlestexcoord[5].w = 2;
+	particlestexcoord[5].h = 2;
+	particlecolors[6].x = 1.0f;
+	particlecolors[6].y = 1.0f;
+	particlecolors[6].z = 1.0f; 
+	particlecolors[6].w = 1.0f;
+	particlestexcoord[6].x = 4;
+	particlestexcoord[6].y = 2;
+	particlestexcoord[6].w = 2;
+	particlestexcoord[6].h = 2;
+	particlecolors[5].x = 0.9f;
+	particlecolors[5].y = 0.9f;
+	particlecolors[5].z = 0.9f;
+	particlecolors[5].w = 1.0f;
+	particlestexcoord[7].x = 6;
+	particlestexcoord[7].y = 2;
+	particlestexcoord[7].w = 2;
+	particlestexcoord[7].h = 2;
+	particlecolors[8].x = 1.0f;
+	particlecolors[8].y = 1.0f;
+	particlecolors[8].z = 1.0f;
+	particlecolors[8].w = 1.0f;
+	particlestexcoord[8].x = 8;
+	particlestexcoord[8].y = 2;
+	particlestexcoord[8].w = 2;
+	particlestexcoord[8].h = 2;
+	lifemodifier[0] = 0.5f;
+	lifemodifier[1] = 0.5f;
+	lifemodifier[2] = 0.5f;
+	lifemodifier[3] = 0.7f;
+	lifemodifier[4] = 0.7f;
+	lifemodifier[5] = 1.0f;
+	lifemodifier[6] = 1.0f;
+	lifemodifier[7] = 1.5f;
+	lifemodifier[8] = 0.4f;
+
+	chaintexcoord.x = 2;
+	chaintexcoord.y = 0;
+	chaintexcoord.w = 1;
+	chaintexcoord.h = 1;
+
+	chainheadtexcoord.x = 3;
+	chainheadtexcoord.y = 0;
+	chainheadtexcoord.w = 2;
+	chainheadtexcoord.h = 1;
+
+
+	// anims
+	anim::setup_hammer(hammeranim);
+	anim::setup_ninja(ninjaanim);
+
+	for (int i = 0; i < NUMHADOKENS; i++)
+	{
+		hadoken[i].x = 1;
+		hadoken[i].y = 12;
+		hadoken[i].w = 7;
+		hadoken[i].h = 4;
+		hadokenparams[i].sizex = 0.0f;
+		hadokenparams[i].sizey = 0.0f;
+		hadokenparams[i].offsetx = 0.0f;
+		hadokenparams[i].offsety = 0.0f;//-hadokenparams[0].sizey * 0.15f;
+	}
+
+	// hadoken
+	hadoken[0].x = 1;
+	hadoken[0].y = 12;
+	hadoken[0].w = 7;
+	hadoken[0].h = 4;
+	hadokenparams[0].sizex = 70.0f * 2.5f;
+	hadokenparams[0].sizey = 40.0f * 2.5f;
+	hadokenparams[0].offsetx = -60.0f;
+	hadokenparams[0].offsety = 0;//-hadokenparams[0].sizey * 0.15f;
+
+	hadoken[2].x = 8;
+	hadoken[2].y = 12;
+	hadoken[2].w = 8;
+	hadoken[2].h = 4;
+	hadokenparams[2].sizex = 80.0f * 2.5f;
+	hadokenparams[2].sizey = 40.0f * 2.5f;
+	hadokenparams[2].offsetx = -60.0f;
+	hadokenparams[2].offsety = 0;//-hadokenparams[1].sizey * 0.5f;
+
+	hadoken[4].x = 17;
+	hadoken[4].y = 12;
+	hadoken[4].w = 7;
+	hadoken[4].h = 4;
+	hadokenparams[4].sizex = 70.0f * 2.5f;
+	hadokenparams[4].sizey = 40.0f * 2.5f;
+	hadokenparams[4].offsetx = -60.0f;
+	hadokenparams[4].offsety = 0;//-hadokenparams[2].sizey * 0.5f;
+
+	// 0 = outline, 1 = body
+	body[0].x = 2;
+	body[0].y = 0;
+	body[0].w = 2;
+	body[0].h = 2;
+	body[1].x = 0;
+	body[1].y = 0;
+	body[1].w = 2;
+	body[1].h = 2;
+
+	feet[0].x = 4;
+	feet[0].y = 1;
+	feet[0].w = 1;
+	feet[0].h = 0.5;
+	feet[1].x = 4;
+	feet[1].y = 1.52;
+	feet[1].w = 1;
+	feet[1].h = 0.48;
+	
+	leye.x = 5;
+	leye.y = 1;
+	leye.w = 0.5;
+	leye.h = 0.5;
+	reye.x = 5;
+	reye.y = 1.5;
+	reye.w = 0.5;
+	reye.h = 0.5;
+}
+
+void modc_entergame()
+{
+	col_init(32);
+	img_init();
+	tilemap_init();
+}
+
+void modc_shutdown()
+{
+}
+
+void modc_newsnapshot()
+{
+	int num = snap_num_items(SNAP_CURRENT);
+	for(int i = 0; i < num; i++)
+	{
+		snap_item item;
+		void *data = snap_get_item(SNAP_CURRENT, i, &item);
+		
+		if(item.type == EVENT_HEALTHMOD)
+		{
+			ev_healthmod *ev = (ev_healthmod *)data;
+			healthmods.create(vec2(ev->x, ev->y), ev->amount);
+		}
+		else if(item.type == EVENT_EXPLOSION)
+		{
+			ev_explosion *ev = (ev_explosion *)data;
+			vec2 p(ev->x, ev->y);
+			
+			// center explosion
+			temp_system.new_particle(p, vec2(0,0), 0.3f, 96.0f, 0, 0.95f);
+			temp_system.new_particle(p, vec2(0,0), 0.3f, 64.0f, 0, 0.95f);
+			temp_system.new_particle(p, vec2(0,0), 0.3f, 32.0f, 0, 0.95f);
+			temp_system.new_particle(p, vec2(0,0), 0.3f, 16.0f, 0, 0.95f);
+			
+			for(int i = 0; i < 16; i++)
+			{
+				vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*128.0f);
+				temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 0, 0.985f);
+			}
+			
+			for(int i = 0; i < 16; i++)
+			{
+				vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(256.0f+frandom()*512.0f);
+				temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 16.0f, 128.0f, 0.985f);
+			}
+
+			for(int i = 0; i < 64; i++)
+			{
+				vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(frandom()*256.0f);
+				temp_system.new_particle(p, v, 0.2f+0.25f*frandom(), 24.0f, 128.0f, 0.985f);
+			}
+		}
+		else if(item.type == EVENT_SMOKE)
+		{
+			ev_explosion *ev = (ev_explosion *)data;
+			vec2 p(ev->x, ev->y);
+			
+			// center explosion
+			vec2 v = normalize(vec2(frandom()-0.5f, -frandom()))*(32.0f+frandom()*32.0f);
+			temp_system.new_particle(p, v, 1.2f, 64.0f, 0, 0.95f);
+			v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f);
+			temp_system.new_particle(p, v, 1.2f, 32.0f, 0, 0.95f);
+			v = normalize(vec2(frandom()-0.5f, -frandom()))*(128.0f+frandom()*128.0f);
+			temp_system.new_particle(p, v, 1.2f, 16.0f, 0, 0.95f);
+			
+			for(int i = 0; i < 8; i++)
+			{
+				vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(64.0f+frandom()*64.0f);
+				temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 0, 0.985f);
+			}
+			
+			for(int i = 0; i < 8; i++)
+			{
+				vec2 v = normalize(vec2(frandom()-0.5f, frandom()-0.5f))*(128.0f+frandom()*256.0f);
+				temp_system.new_particle(p, v, 0.5f+0.5f*frandom(), 16.0f, 128.0f, 0.985f);
+			}
+		}
+		else if(item.type == EVENT_SOUND)
+		{
+			ev_sound *ev = (ev_sound *)data;
+			vec2 p(ev->x, ev->y);
+			int sound = (ev->sound & SOUND_MASK);
+			bool bstartloop = (ev->sound & SOUND_LOOPFLAG_STARTLOOP) != 0;
+			bool bstoploop = (ev->sound & SOUND_LOOPFLAG_STOPLOOP) != 0;
+			float vol, pan;
+			sound_vol_pan(p, &vol, &pan);
+
+			switch(sound)
+			{
+
+			// FIRE!
+			case SOUND_FIRE_GUN:
+				sound_gun_fire.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_FIRE_SHOTGUN:
+				sound_shotty_fire.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_FIRE_ROCKET:
+				sound_flump_launch.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_FIRE_MELEE:
+				sound_hammer_swing.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_FIRE_NINJA:
+				sound_ninja_attack.play_random(volume_gun*vol, pan);
+				break;
+
+			// IMPACT
+			case SOUND_IMPACT_PROJECTILE_GUN:
+				break;
+			case SOUND_IMPACT_PROJECTILE_SHOTGUN:
+				break;
+			case SOUND_IMPACT_PROJECTILE_ROCKET:
+				sound_flump_explode.play_random(volume_hit*vol, pan);
+				break;
+
+			// PLAYER
+			case SOUND_PLAYER_JUMP:
+				sound_body_jump.play_random(volume_tee*vol, pan);
+				break;
+			case SOUND_PLAYER_HURT_SHORT:
+				sound_pain_short.play_random(volume_tee*vol, pan);
+				break;
+			case SOUND_PLAYER_HURT_LONG:
+				sound_pain_long.play_random(volume_tee*vol, pan);
+				break;
+			case SOUND_PLAYER_SPAWN:
+				sound_spawn.play_random(volume_tee*vol, pan);
+				break;
+			case SOUND_PLAYER_CHAIN_LOOP:
+				sound_hook_loop.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_PLAYER_CHAIN_IMPACT:
+				sound_hook_attach.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_PLAYER_IMPACT:
+				sound_body_land.play_random(volume_hit*vol, pan);
+				break;
+			case SOUND_PLAYER_IMPACT_NINJA:
+				sound_ninja_hit.play_random(volume_hit*vol, pan);
+				break;
+			case SOUND_PLAYER_DIE:
+				sound_body_splat.play_random(volume_tee*vol, pan);
+				break;
+			case SOUND_PLAYER_SWITCHWEAPON:
+				sound_weapon_switch.play_random(volume_gun*vol, pan);
+				break;
+			case SOUND_PLAYER_EQUIP:
+				break;
+			case SOUND_PLAYER_LAND:
+				sound_body_land.play_random(volume_tee*vol, pan);
+				break;
+			}
+		}
+	}
+}
+
+static void render_projectile(obj_projectile *prev, obj_projectile *current)
+{
+	gfx_texture_set(texture_weapon);
+	gfx_quads_begin();
+	cell_select_ex(numcellsx,numcellsy,weaponprojtexcoord[current->type].x, weaponprojtexcoord[current->type].y, weaponprojtexcoord[current->type].w, weaponprojtexcoord[current->type].h);
+	vec2 vel(current->vx, current->vy);
+	
+	// TODO: interpolare angle aswell
+	if(length(vel) > 0.00001f)
+		gfx_quads_setrotation(get_angle(vel));
+	else
+		gfx_quads_setrotation(0);
+	
+	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick());
+	gfx_quads_draw(pos.x, pos.y,32,32);
+	gfx_quads_setrotation(0);
+	gfx_quads_end();
+}
+
+static void render_powerup(obj_powerup *prev, obj_powerup *current)
+{
+	//dbg_msg("client", "rendering powerup at %d,%d", current->x, current->y);
+	
+	gfx_texture_set(texture_weapon);
+	gfx_quads_begin();
+	float angle = 0.0f;
+	float sizex = 64.0f;
+	float sizey = 64.0f;
+	if (current->type == POWERUP_TYPE_WEAPON)
+	{
+		angle = -0.25f * pi * 2.0f;
+		cell_select_ex(numcellsx,numcellsy,weapontexcoord[current->subtype].x, weapontexcoord[current->subtype].y, weapontexcoord[current->subtype].w, weapontexcoord[current->subtype].h);
+		sizex = weaponrenderparams[current->subtype].sizex;
+		sizey = weaponrenderparams[current->subtype].sizey;
+	}
+	else
+		cell_select_ex(numcellsx,numcellsy,poweruptexcoord[current->type].x, poweruptexcoord[current->type].y, poweruptexcoord[current->type].w, poweruptexcoord[current->type].h);
+	vec2 vel(current->vx, current->vy);
+	
+	gfx_quads_setrotation(angle);
+	// TODO: interpolare angle aswell
+	/*if(length(vel) > 0.00001f)
+		gfx_quads_setrotation(get_angle(vel));
+	else
+		gfx_quads_setrotation(0);*/
+	
+	vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick());
+	float offset = pos.y/32.0f + pos.x/32.0f;
+	gfx_quads_draw(pos.x+cosf(client_localtime()*2.0f+offset)*2.5f, pos.y+sinf(client_localtime()*2.0f+offset)*2.5f,sizex * 0.65f,sizey * 0.65f);
+	gfx_quads_setrotation(0);
+	gfx_quads_end();
+}
+
+float getmeleeangle(vec2 direction, obj_player* prev, obj_player* player)
+{
+	vec2 meleedir(0.53, -0.84);
+	meleedir = normalize(meleedir);
+	vec2 meleedirattack(0.95, -0.3);
+	meleedirattack = normalize(meleedirattack);
+
+	if(direction.x < 0)
+	{
+		meleedir.x = -meleedir.x;
+		meleedirattack.x = -meleedirattack.x;
+	}
+
+	// 0 -> visualtimeattack go to end pose, (len - visualime) -> go back to normal pose
+
+	float angle = get_angle(meleedir);
+	if (prev->attackticks)
+	{
+		float angleattack = get_angle(meleedirattack);
+		int phase1tick = (player->attacklen - player->attackticks);
+		if (phase1tick < player->visualtimeattack)
+		{
+			float intratick = snap_intratick();
+			float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
+			angle = LERP(angle, angleattack, min(1.0f,max(0.0f,t)));
+		}
+		else
+		{
+			// go back to normal pose
+			int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks);
+			float intratick = snap_intratick();
+			float t = ((((float)phase2tick) + intratick)/(float)player->visualtimeattack);
+			angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t)));
+		}
+	}
+	/*if (prev->attackticks && !player->attackticks)
+	{
+		// blend back to normal
+		float angleattack = get_angle(meleedirattack);
+		angle = LERP(angleattack, angle, min(1.0f,max(0.0f,snap_intratick())));
+	}
+	else if (player->attackticks)
+	{
+		float angleattack = get_angle(meleedirattack);
+		float intratick = snap_intratick();
+		float t = ((((float)player->attackticks) - intratick)/(float)player->attacklen);
+		angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t)));
+	}*/
+
+	return angle;
+}
+
+float gethammereangle(vec2 direction, obj_player* prev, obj_player* player)
+{
+	float t = 0.0f;
+	if (prev->attackticks)
+		t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen);
+
+	vec2 pos;
+	float angle = 0.0f;
+	hammeranim.evalanim(t,pos,angle);
+	if(direction.x < 0)
+		angle = pi -angle;// + ;
+	//dbg_msg("anim", "Time: %f", t);
+	return angle;
+}
+
+float getninjaangle(vec2 direction, obj_player* prev, obj_player* player)
+{
+	float t = 0.0f;
+	if (prev->attackticks)
+		t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen);
+
+	vec2 pos;
+	float angle = 0.0f;
+	ninjaanim.evalanim(t,pos,angle);
+	if(direction.x < 0)
+		angle = pi -angle;// + ;
+	//dbg_msg("anim", "Time: %f", t);
+	return angle;
+}
+
+
+float getrecoil(obj_player* prev, obj_player* player)
+{
+	// attack = -10
+	float recoil = 0.0f;
+	if (prev->attackticks)
+	{
+		float attackrecoil = recoils[player->weapon];
+		int phase1tick = (player->attacklen - player->attackticks);
+		if (phase1tick < player->visualtimeattack)
+		{
+			float intratick = snap_intratick();
+			float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
+			recoil = LERP(0, attackrecoil, min(1.0f,max(0.0f,t)));
+		}
+		else
+		{
+			// go back to normal pose
+			int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks);
+			float intratick = snap_intratick();
+			float t = ((((float)phase2tick) + intratick)/(float)(player->attacklen - player->visualtimeattack));
+			recoil = LERP(attackrecoil, 0.0f, min(1.0f,max(0.0f,t)));
+		}
+	}
+	return recoil;
+}
+
+static void render_player(obj_player *prev, obj_player *player)
+{
+	vec2 direction = get_direction(player->angle);
+	float angle = player->angle/256.0f;
+	vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), snap_intratick());
+	
+	// draw hook
+	if(player->hook_active)
+	{
+		gfx_texture_set(texture_weapon);
+		gfx_quads_begin();
+		//gfx_quads_begin();
+
+		vec2 pos = position;
+		
+		vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), snap_intratick());
+		
+		float d = distance(pos, hook_pos);
+		vec2 dir = normalize(pos-hook_pos);
+
+		gfx_quads_setrotation(get_angle(dir)+pi);
+		
+		// render head
+		cell_select_ex(numcellsx,numcellsy, chainheadtexcoord.x,chainheadtexcoord.y, chainheadtexcoord.w, chainheadtexcoord.h);
+		gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16);
+		
+		// render chain
+		cell_select_ex(numcellsx,numcellsy, chaintexcoord.x, chaintexcoord.y, chaintexcoord.w, chaintexcoord.h);
+		for(float f = 24; f < d; f += 24)
+		{
+			vec2 p = hook_pos + dir*f;
+			gfx_quads_draw(p.x, p.y,24,16);
+		}
+		
+		gfx_quads_setrotation(0);
+		gfx_quads_end();
+	}
+
+	// draw gun
+	{
+		gfx_texture_set(texture_weapon);
+		gfx_quads_begin();
+		gfx_quads_setrotation(angle);
+
+		if (player->modifier & (1 << MODIFIER_TYPE_NINJA))
+		{
+			float playerangle = angle;
+			// render NINJA!!! (0.53, 0.84) when idle to -> (0.95, 0.3) at the end of attack
+			if(direction.x < 0)
+				cell_select_ex_flip_y(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h);
+			else
+				cell_select_ex(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h);
+
+			angle = getninjaangle(direction, prev, player);//getmeleeangle(direction, prev, player);
+			vec2 ninjadir = get_direction(angle * 256.0f);
+			gfx_quads_setrotation(angle);
+			vec2 p = position + vec2(0,modifierrenderparams[MODIFIER_TYPE_NINJA].offsety)+ ninjadir * modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx;
+			// if attack is active hold it differently and draw speedlines behind us?
+			gfx_quads_draw(p.x,p.y/*+bob*/,modifierrenderparams[MODIFIER_TYPE_NINJA].sizex, modifierrenderparams[MODIFIER_TYPE_NINJA].sizey);
+
+			if ((player->attacklen - player->attackticks) <= (SERVER_TICK_SPEED / 5))
+			{
+				gfx_quads_setrotation(playerangle);
+				int ihadoken = rand() % NUMHADOKENS;
+				cell_select_ex(numcellsx, numcellsy, hadoken[ihadoken].x, hadoken[ihadoken].y, hadoken[ihadoken].w, hadoken[ihadoken].h);
+				vec2 p = position + vec2(0,hadokenparams[ihadoken].offsety)+ direction * hadokenparams[ihadoken].offsetx;
+				gfx_quads_draw(p.x,p.y/*+bob*/,hadokenparams[ihadoken].sizex, hadokenparams[ihadoken].sizey);
+			}
+		}
+		else
+		{
+			// normal weapons
+			if(direction.x < 0)
+				cell_select_ex_flip_y(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h);
+			else
+				cell_select_ex(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h);
+
+			vec2 dir = direction;
+			float recoil = 0.0f;
+			if (player->weapon == WEAPON_TYPE_MELEE)
+			{
+				// if attack is under way, bash stuffs
+				//angle = getmeleeangle(direction, prev, player);
+				angle = gethammereangle(direction, prev, player);
+				gfx_quads_setrotation(angle);
+				dir = get_direction(angle * 256.0f);
+			}
+			else
+			{
+				recoil = getrecoil(prev, player);
+			}
+
+			vec2 p = position + vec2(0,weaponrenderparams[player->weapon].offsety) + dir * weaponrenderparams[player->weapon].offsetx - dir * recoil;
+			gfx_quads_draw(p.x,p.y/*+bob*/,weaponrenderparams[player->weapon].sizex, weaponrenderparams[player->weapon].sizey);
+			// draw muzzleflare
+			if (player->weapon == WEAPON_TYPE_GUN || player->weapon == WEAPON_TYPE_SHOTGUN)
+			{
+				// check if we're firing stuff
+				if (true)///prev->attackticks)
+				{
+					float alpha = 0.0f;
+					int phase1tick = (player->attacklen - player->attackticks);
+					if (phase1tick < (player->visualtimeattack + 3))
+					{
+						float intratick = snap_intratick();
+						float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack);
+						alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t)));
+					}
+
+					if (alpha > 0.0f)
+					{
+						float offsety = -muzzleparams[player->weapon].offsety;
+						int itex = rand() % nummuzzletex[player->weapon];
+						if(direction.x < 0)
+						{
+							offsety = -offsety;
+							cell_select_ex_flip_y(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h);
+						}
+						else
+							cell_select_ex(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h);
+
+						gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha);
+						vec2 diry(-dir.y,dir.x);
+						p += dir * muzzleparams[player->weapon].offsetx + diry * offsety;
+						gfx_quads_draw(p.x,p.y/*+bob*/,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey);
+					}
+				}
+			}
+		}
+		/*else
+		{
+			// minigun
+			if(direction.x < 0)
+				cell_select_flip_y(4,4,8,2);
+			else
+				cell_select(4,4,8,2);
+			vec2 p = position + vec2(0,3);
+			gfx_quads_draw(p.x,p.y,8*8,8*2);
+		}*/
+			
+		gfx_quads_setrotation(0);
+		gfx_quads_end();
+	}
+	
+	
+	gfx_texture_set(texture_char_default);
+	gfx_quads_begin();
+	
+	float bob = 0;
+	
+	// draw foots
+	const float cyclelength = 128.0f;
+	const float steplength = 26;
+	const float lift = 4.0f;
+	bool stationary = player->vx < 1 && player->vx > -1;
+	bool inair = col_check_point(player->x, player->y+16) == 0;
+	
+	for(int p = 0; p < 2; p++)
+	{
+		// first pass we draw the outline
+		// second pass we draw the filling
+		
+		//int v_offset = p?0:5;
+		int outline = p;// ? 1 : 0;
+		float offsety = charids[player->clientid % 16] * 2.0f;
+		
+		for(int f = 0; f < 2; f++)
+		{
+			float basesize = 10.0f;
+			if(f == 1)
+			{
+				// draw body
+				float t = fmod(position.x, cyclelength/2)/(cyclelength/2);
+				bob = -sinf(pow(t,2)*pi) * 3;
+				cell_select_ex(charnumcellsx,charnumcellsy, body[outline].x,body[outline].y + offsety,body[outline].w,body[outline].h);
+				//cell_select_ex(16,16, 0,0+v_offset,4,4);
+				//const float size = 64.0f;
+				if(stationary || inair)
+					bob = 0;
+				gfx_quads_draw(position.x, position.y-5+bob, 4*basesize, 4*basesize);
+				
+				// draw eyes
+				if(p == 1)
+				{
+					//cell_select_ex(16,16, 8,3,1,1);
+					vec2 md = get_direction(player->angle);
+					float mouse_dir_x = md.x;
+					float mouse_dir_y = md.y;
+					
+					// normal
+					cell_select_ex(charnumcellsx,charnumcellsy, leye.x,leye.y + offsety,leye.w,leye.h);
+					gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize);
+					cell_select_ex(charnumcellsx,charnumcellsy, reye.x,reye.y + offsety,reye.w,reye.h);
+					gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize);
+				}
+			}
+
+			// draw feet
+			//cell_select_ex(16,16, 5,2+v_offset, 2,2);
+			cell_select_ex(charnumcellsx,charnumcellsy, feet[outline].x,feet[outline].y + offsety, feet[outline].w,feet[outline].h);
+			float w = basesize*2.5f;
+			float h = basesize*1.425f;
+			if(inair)
+			{
+				float r = 0.0f;
+				if(player->vy < 0.0f)
+					r = player->vy/3.0f;
+				else
+					r = player->vy/15.0f;
+
+				// clamp the rotation
+				if(r > 0.5f) r = 0.5f;
+				if(r < -0.5f) r = -0.5f;
+				
+				if(player->vx > 0.0f)
+					r *= -1.0f;
+				gfx_quads_setrotation(r);
+				gfx_quads_drawTL(position.x-4+f*7-w/2, position.y+16 - h, w, h);
+				gfx_quads_setrotation(0);
+			}
+			else if(stationary)
+			{
+				// stationary
+				gfx_quads_drawTL(position.x-7+f*14-w/2, position.y+16 - h, w, h);
+			}
+			else
+			{
+				/*
+					The walk cycle, 2 parts
+					
+						  111  
+						 1   1   
+						2     1
+						 2     1
+						  2222221 
+					GROUND GROUND GROUND
+				*/
+				
+				// moving
+				float tx = position.x+f*(cyclelength/2);
+				float t = fmod(tx, cyclelength) / cyclelength;
+				if(player->vx < 0)
+					t = 1.0f-t;
+				
+				float y;
+				float x = 0;
+				float r = 0;
+				float r_back = 1.5f;
+				
+				if(t < 0.5f)
+				{
+					// stomp down foot (part 1)
+					float st = t*2;
+					y = 1.0f-pow(st, 0.5f) + sinf(pow(st,2)*pi)*0.5f;
+					x = -steplength/2 + st*steplength;
+					r = r_back*(1-st) + sinf(pow(st,1.5f)*pi*2);
+				}
+				else
+				{
+					// lift foot up again (part 2)
+					float st = (t-0.5f)*2;
+					y = pow(st, 5.0f);
+					x = steplength/2 - st*steplength;
+					r = y*r_back;
+				}
+				
+
+				if(player->vx > 0)
+				{
+					gfx_quads_setrotation(r);
+					gfx_quads_drawTL(position.x+x-w/2, position.y+16-y*lift - h, w, h);
+				}
+				else
+				{
+					gfx_quads_setrotation(-r);
+					gfx_quads_drawTL(position.x-x-w/2, position.y+16-y*lift - h, w, h);
+				}
+				gfx_quads_setrotation(0);
+			}
+
+		}
+	}
+	
+	gfx_quads_end();
+	
+	
+}
+
+static player_input oldinput;
+static bool bfirst = true;
+void modc_render()
+{	
+	if (bfirst)
+	{
+		bfirst = false;
+		oldinput.activeweapon = 0;
+		oldinput.angle = 0;
+		oldinput.blink = 0;
+		oldinput.fire = 0;
+		oldinput.hook = 0;
+		oldinput.jump = 0;
+		oldinput.left = 0;
+		oldinput.right = 0;
+	}
+	// fetch new input
+	{
+		int x, y;
+		inp_mouse_relative(&x, &y);
+		mouse_pos += vec2(x, y);
+		float l = length(mouse_pos);
+		if(l > 600.0f)
+			mouse_pos = normalize(mouse_pos)*600.0f;
+	}
+	
+	// snap input
+	{
+		player_input input;
+		input.left = inp_key_pressed('A');
+		input.right = inp_key_pressed('D');
+		float a = atan((float)mouse_pos.y/(float)mouse_pos.x);
+		if(mouse_pos.x < 0)
+			a = a+pi;
+		input.angle = (int)(a*256.0f);
+		input.jump = inp_key_pressed(baselib::keys::space) || inp_key_pressed('W');
+		
+		input.fire = inp_mouse_button_pressed(0);// | (oldinput.fire << 16);
+		//oldinput.fire = input.fire & 0x0000ffff;
+		
+		input.hook = inp_mouse_button_pressed(1) || inp_key_pressed(baselib::keys::lctrl); // be nice to mac users O.o
+		input.blink = inp_key_pressed('S');
+		
+		// Weapon switching
+		input.activeweapon = inp_key_pressed('1') ? 0x80000000 : 0;
+		if (!input.activeweapon)
+			input.activeweapon = inp_key_pressed('2') ? 0x80000000 | 1 : 0;
+		if (!input.activeweapon)
+			input.activeweapon = inp_key_pressed('3') ? 0x80000000 | 2 : 0;
+		if (!input.activeweapon)
+			input.activeweapon = inp_key_pressed('4') ? 0x80000000 | 3 : 0;
+		/*if (!input.activeweapon)
+			input.activeweapon = inp_key_pressed('5') ? 0x80000000 | 4 : 0;*/
+
+		snap_input(&input, sizeof(input));
+	}
+
+	// setup world view
+	{
+		// 1. fetch local player
+		// 2. set him to the center
+		
+		int num = snap_num_items(SNAP_CURRENT);
+		for(int i = 0; i < num; i++)
+		{
+			snap_item item;
+			void *data = snap_get_item(SNAP_CURRENT, i, &item);
+			
+			if(item.type == OBJTYPE_PLAYER)
+			{
+				obj_player *player = (obj_player *)data;
+				if(player->local)
+				{
+					local_player = player;
+					local_player_pos = vec2(player->x, player->y);
+
+					void *p = snap_find_item(SNAP_PREV, item.type, item.id);
+					if(p)
+						local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, snap_intratick());
+					break;
+				}
+			}
+		}
+	}
+
+	// pseudo format
+	float zoom = inp_key_pressed('T') ? 1.0 : 3.0f;
+	
+	float width = 400*zoom;
+	float height = 300*zoom;
+	float screen_x = 0;
+	float screen_y = 0;
+	
+	// center at char but can be moved when mouse is far away
+	float offx = 0, offy = 0;
+	int deadzone = 300;
+	if(mouse_pos.x > deadzone) offx = mouse_pos.x-deadzone;
+	if(mouse_pos.x <-deadzone) offx = mouse_pos.x+deadzone;
+	if(mouse_pos.y > deadzone) offy = mouse_pos.y-deadzone;
+	if(mouse_pos.y <-deadzone) offy = mouse_pos.y+deadzone;
+	offx = offx*2/3;
+	offy = offy*2/3;
+		
+	screen_x = local_player_pos.x+offx;
+	screen_y = local_player_pos.y+offy;
+	
+	gfx_mapscreen(screen_x-width/2, screen_y-height/2, screen_x+width/2, screen_y+height/2);
+	
+	// draw background
+	gfx_clear(0.65f,0.78f,0.9f);
+	
+	{
+
+		vec2 pos(local_player_pos.x*0.5f, local_player_pos.y*0.5f);
+
+		gfx_texture_set(-1);
+		gfx_blend_additive();
+		gfx_quads_begin();
+		const int rays = 10;
+		gfx_quads_setcolor(1.0f,1.0f,1.0f,0.025f);
+		for(int r = 0; r < rays; r++)
+		{
+			float a = r/(float)rays + client_localtime()*0.05f;
+			float size = (1.0f/(float)rays)*0.25f;
+			vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f));
+			vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f));
+			
+			//gfx_quads_draw_freeform(0,0, -100,0, -100,-100, 0,-100);
+			
+			gfx_quads_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f);
+			gfx_quads_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f);
+			gfx_quads_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f);
+			gfx_quads_setcolorvertex(3, 1.0f,1.0f,1.0f,0.0f);
+			const float range = 1000.0f;
+			gfx_quads_draw_freeform(
+				pos.x+dir0.x, pos.y+dir0.y,
+				pos.x+dir1.x, pos.y+dir1.y,
+				pos.x+dir0.x*range, pos.y+dir0.y*range,
+				pos.x+dir1.x*range, pos.y+dir1.y*range);
+		}
+		gfx_quads_end();
+		gfx_blend_normal();
+		
+		gfx_texture_set(texture_sun);
+		gfx_quads_begin();
+		gfx_quads_draw(pos.x, pos.y, 256, 256);
+		gfx_quads_end();
+	}
+	
+	// render map
+	tilemap_render(32.0f, 0);
+#ifdef _DEBUG
+	float speed = 0.0f;
+#endif
+	// render items
+	int num = snap_num_items(SNAP_CURRENT);
+	for(int i = 0; i < num; i++)
+	{
+		snap_item item;
+		void *data = snap_get_item(SNAP_CURRENT, i, &item);
+		
+		if(item.type == OBJTYPE_PLAYER)
+		{
+			void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
+			if(prev)
+			{
+				render_player((obj_player *)prev, (obj_player *)data);
+/*#ifdef _DEBUG
+				{
+					obj_player *p = (obj_player *)prev;
+					obj_player *c = (obj_player *)data;
+					vec2 positionold = vec2(p->x, p->y);
+					vec2 poscur = vec2(c->x, c->y);
+					speed = distance(positionold,poscur);
+				}
+#endif*/
+			}
+		}
+		else if(item.type == OBJTYPE_PROJECTILE)
+		{
+			void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
+			if(prev)
+				render_projectile((obj_projectile *)prev, (obj_projectile *)data);
+		}
+		else if(item.type == OBJTYPE_POWERUP)
+		{
+			void *prev = snap_find_item(SNAP_PREV, item.type, item.id);
+			if(prev)
+				render_powerup((obj_powerup*)prev, (obj_powerup *)data);
+		}
+	}
+
+	// render particles
+	temp_system.update(client_frametime());
+	temp_system.render();
+
+	tilemap_render(32.0f, 1);
+	
+	// render health mods
+	healthmods.render();
+	
+	// render cursor 
+	// FIXME CURSOR!!!
+	
+	if(local_player)
+	{
+		gfx_texture_set(texture_weapon);
+		gfx_quads_begin();
+		if (local_player->modifier & (1 << MODIFIER_TYPE_NINJA))
+			cell_select_ex(numcellsx,numcellsy, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h);
+		else
+			cell_select_ex(numcellsx,numcellsy, weapontexcoordcursor[local_player->weapon].x, weapontexcoordcursor[local_player->weapon].y, weapontexcoordcursor[local_player->weapon].w, weapontexcoordcursor[local_player->weapon].h);
+		float cursorsize = 64;
+		gfx_quads_draw(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y,cursorsize,cursorsize);
+
+
+		// render ammo count
+		// render gui stuff
+		gfx_quads_end();
+		gfx_quads_begin();
+		gfx_mapscreen(0,0,400,300);
+		cell_select_ex(numcellsx,numcellsy, weaponprojtexcoord[local_player->weapon].x, weaponprojtexcoord[local_player->weapon].y, weaponprojtexcoord[local_player->weapon].w, weaponprojtexcoord[local_player->weapon].h);
+		for (int i = 0; i < local_player->ammocount; i++)
+		{
+			gfx_quads_drawTL(10+i*12,34,10,10);
+		}
+		gfx_quads_end();
+
+		gfx_texture_set(texture_game);
+		gfx_quads_begin();
+		int h = 0;
+		cell_select_ex(32,16, 0,0, 4,4);
+		for(; h < local_player->health; h++)
+			gfx_quads_drawTL(10+h*12,10,10,10);
+		
+		cell_select_ex(32,16, 5,0, 4,4);
+		for(; h < 10; h++)
+			gfx_quads_drawTL(10+h*12,10,10,10);
+
+		h = 0;
+		cell_select_ex(32,16, 0,5, 4,4);
+		for(; h < local_player->armor; h++)
+			gfx_quads_drawTL(10+h*12,22,10,10);
+		
+		cell_select_ex(32,16, 5,5, 4,4);
+		for(; h < 10; h++)
+			gfx_quads_drawTL(10+h*12,22,10,10);
+		gfx_quads_end();
+
+		// render speed
+/*#ifdef _DEBUG
+		gfx_texture_set(font_texture);
+		char text[256];
+		sprintf(text,"speed: %f",speed);
+		gfx_quads_text(300,20,10,text);
+#endif*/
+	}
+	// render gui stuff
+	gfx_mapscreen(0,0,400,300);
+	// render score board
+	if(inp_key_pressed(baselib::keys::tab))
+	{
+		gfx_texture_set(font_texture);
+		gfx_quads_text(10, 50, 8, "Score Board");
+
+		int num = snap_num_items(SNAP_CURRENT);
+		int row = 1;
+		for(int i = 0; i < num; i++)
+		{
+			snap_item item;
+			void *data = snap_get_item(SNAP_CURRENT, i, &item);
+			
+			if(item.type == OBJTYPE_PLAYER)
+			{
+				obj_player *player = (obj_player *)data;
+				if(player)
+				{
+					char buf[128];
+					char name[32];
+					snap_decode_string(player->name, name, 32);
+					sprintf(buf, "%4d %s", player->score, name);
+					gfx_quads_text(10, 50 + 10 * row, 8, buf);
+					row++;
+				}
+			}
+		}
+	}
+}
diff --git a/src/game/game_server.cpp b/src/game/game_server.cpp
new file mode 100644
index 00000000..5e93165a
--- /dev/null
+++ b/src/game/game_server.cpp
@@ -0,0 +1,2122 @@
+#include <stdlib.h>
+#include <string.h>
+#include "game.h"
+
+using namespace baselib;
+
+// --------- PHYSICS TWEAK! --------
+const float ground_control_speed = 7.0f;
+const float ground_control_accel = 2.0f;
+const float ground_friction = 0.5f;
+const float ground_jump_speed = 12.0f;
+const float air_control_speed = 3.5f;
+const float air_control_accel = 1.2f;
+const float air_friction = 0.95f;
+const float hook_length = 32*10.0f;
+const float hook_fire_speed = 45.0f;
+const float hook_drag_accel = 3.0f;
+const float hook_drag_speed = 15.0f;
+const float gravity = 0.5f;
+
+class player* get_player(int index);
+void create_healthmod(vec2 p, int amount);
+void create_explosion(vec2 p, int owner = -1, bool bnodamage = false);
+void create_smoke(vec2 p);
+void create_sound(vec2 pos, int sound, int loopflags = 0);
+class player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, class entity* notthis = 0);
+
+// TODO: rewrite this smarter!
+void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity)
+{
+	// do the move
+	vec2 pos = *inout_pos;
+	vec2 vel = *inout_vel;
+	
+	float distance = length(vel);
+	int max = (int)distance;
+	
+	vec2 offsets[4] = {	vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2),
+						vec2(-size.x/2,  size.y/2), vec2( size.x/2,  size.y/2)};
+	
+	if(distance > 0.00001f)
+	{
+		vec2 old_pos = pos;
+		for(int i = 0; i <= max; i++)
+		{
+			float amount = i/(float)max;
+			if(max == 0)
+				amount = 0;
+			
+			vec2 new_pos = pos + vel*amount; // TODO: this row is not nice
+
+			for(int p = 0; p < 4; p++)
+			{
+				vec2 np = new_pos+offsets[p];
+				vec2 op = old_pos+offsets[p];
+				if(col_check_point(np))
+				{
+					int affected = 0;
+					if(col_check_point(np.x, op.y))
+					{
+						vel.x = -vel.x*elasticity;
+						pos.x = old_pos.x;
+						new_pos.x = old_pos.x;
+						affected++;
+					}
+
+					if(col_check_point(op.x, np.y))
+					{
+						vel.y = -vel.y*elasticity;
+						pos.y = old_pos.y;
+						new_pos.y = old_pos.y;
+						affected++;
+					}
+					
+					if(!affected)
+					{
+						new_pos = old_pos;
+						pos = old_pos;
+						vel *= -elasticity;
+					}
+				}
+			}
+			
+			old_pos = new_pos;
+		}
+		
+		pos = old_pos;
+	}
+	
+	*inout_pos = pos;
+	*inout_vel = vel;
+}
+
+// TODO: rewrite this smarter!
+bool intersect_line(vec2 pos0, vec2 pos1, vec2 *out)
+{
+	float d = distance(pos0, pos1);
+	
+	for(float f = 0; f < d; f++)
+	{
+		float a = f/d;
+		vec2 pos = mix(pos0, pos1, a);
+		if(col_check_point(pos))
+		{
+			if(out)
+				*out = pos;
+			return true;
+		}
+	}
+	if(out)
+		*out = pos1;
+	return false;
+}
+
+//
+class event_handler
+{
+	static const int MAX_EVENTS = 128;
+	static const int MAX_DATASIZE = 128*4;
+
+	int types[MAX_EVENTS];  // TODO: remove some of these arrays
+	int offsets[MAX_EVENTS];
+	int sizes[MAX_EVENTS];
+	char data[MAX_DATASIZE];
+	
+	int current_offset;
+	int num_events;
+public:
+	event_handler()
+	{
+		num_events = 0;
+	}
+	
+	void *create(int type, int size)
+	{
+		void *p = &data[current_offset];
+		offsets[num_events] = current_offset;
+		types[num_events] = type;
+		sizes[num_events] = size;
+		current_offset += size;
+		num_events++;
+		return p;
+	}
+	
+	void clear()
+	{
+		num_events = 0;
+		current_offset = 0;
+	}
+	
+	void snap(int snapping_client)
+	{
+		for(int i = 0; i < num_events; i++)
+		{
+			void *d = snap_new_item(types[i], i, sizes[i]);
+			mem_copy(d, &data[offsets[i]], sizes[i]);
+		}
+	}
+};
+
+static event_handler events;
+/*
+template<typename T, int SIZE>
+class pool
+{
+	struct element
+	{
+		int next_free;
+		T data;
+	};
+	
+	element elements[SIZE];
+	int first_free;
+public:
+	pool()
+	{
+		first_free = 0;
+		for(int i = 0; i < SIZE; i++)
+			elements[i].next_free = i+1;
+		elements[SIZE-1].next_free = -1;
+	}
+};*/
+
+// a basic entity
+class entity
+{
+private:
+	friend class game_world;
+	friend class player;
+	entity *prev_entity;
+	entity *next_entity;
+	int index;
+	
+public:
+	vec2 pos;
+	float proximity_radius;
+	unsigned flags;
+	int objtype;
+
+	enum
+	{
+		FLAG_DESTROY=0x00000001,
+	};
+
+	entity(int objtype)
+	{
+		this->objtype = objtype;
+		pos = vec2(0,0);
+		flags = 0;
+		proximity_radius = 0;
+	}
+	
+	virtual ~entity()
+	{
+	}
+
+	virtual void destroy() { delete this; }
+	virtual void tick() {}
+	virtual void snap(int snapping_client) {}
+		
+	virtual bool take_damage(vec2 force, int dmg, int from) { return true; }
+};
+
+class powerup : public entity
+{
+public:
+	static const int phys_size = 14;
+	enum
+	{
+		POWERUP_FLAG_HOOKABLE = 1 << 0,
+	};
+	vec2 vel;
+	class player* playerhooked;
+	int type;
+	int id;
+	int subtype; // weapon type for instance?
+	int numitems; // number off powerup items
+	int flags;
+	int spawntick;
+	powerup(int _type, int _subtype = 0, int _numitems = 0, int _flags = 0);
+
+	static void spawnrandom(int _lifespan);
+
+	void tick();
+
+	void snap(int snapping_client);
+};
+
+// game world. handles all entities
+class game_world
+{
+public:
+	entity *first_entity;
+	game_world()
+	{
+		first_entity = 0x0;
+	}
+	
+	int find_entities(vec2 pos, float radius, entity **ents, int max)
+	{
+		int num = 0;
+		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+		{
+			if(distance(ent->pos, pos) < radius+ent->proximity_radius)
+			{
+				ents[num] = ent;
+				num++;
+				if(num == max)
+					break;
+			}
+		}
+		
+		return num;
+	}
+
+	int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes)
+	{
+		int num = 0;
+		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+		{
+			for (int i = 0; i < maxtypes; i++)
+			{
+				if (ent->objtype != types[i])
+					continue;
+
+				if(distance(ent->pos, pos) < radius+ent->proximity_radius)
+				{
+					ents[num] = ent;
+					num++;
+					if(num == max)
+						break;
+				}
+			}
+		}
+		
+		return num;
+	}
+	
+	void insert_entity(entity *ent)
+	{
+		// insert it
+		if(first_entity)
+			first_entity->prev_entity = ent;
+		ent->next_entity = first_entity;
+		ent->prev_entity = 0x0;
+		first_entity = ent;
+	}
+	
+	void destroy_entity(entity *ent)
+	{
+		ent->flags |= entity::FLAG_DESTROY;
+		// call destroy
+		//remove_entity(ent);
+		//ent->destroy();
+	}
+	
+	void remove_entity(entity *ent)
+	{
+		// remove
+		if(ent->prev_entity)
+			ent->prev_entity->next_entity = ent->next_entity;
+		else
+			first_entity = ent->next_entity;
+		if(ent->next_entity)
+			ent->next_entity->prev_entity = ent->prev_entity;
+	}
+	
+	//
+	void snap(int snapping_client)
+	{
+		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+			ent->snap(snapping_client);
+	}
+	
+	void tick()
+	{
+		// update all objects
+		for(entity *ent = first_entity; ent; ent = ent->next_entity)
+			ent->tick();
+		
+		// destroy objects marked for destruction
+		entity *ent = first_entity;
+		while(ent)
+		{
+			entity *next = ent->next_entity;
+			if(ent->flags&entity::FLAG_DESTROY)
+			{
+				remove_entity(ent);
+				ent->destroy();
+			}
+			ent = next;
+		}
+	}
+};
+
+static game_world world;
+
+// projectile entity
+class projectile : public entity
+{
+public:
+	enum
+	{
+		PROJECTILE_FLAGS_EXPLODE = 1 << 0,
+	};
+	vec2 vel;
+	entity* powner;
+	int lifespan;
+	int id;
+	int owner;
+	int type;
+	int flags;
+	int damage;
+	int sound_impact;
+	float force;
+	
+	projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) :
+		entity(OBJTYPE_PROJECTILE)
+	{
+		static int current_id = 0;
+		this->id = current_id++;
+		this->type = type;
+		this->pos = pos;
+		this->vel = vel;
+		this->lifespan = span;
+		this->owner = owner;
+		this->powner = powner;
+		this->flags = flags;
+		this->force = force;
+		this->damage = damage;
+		this->sound_impact = sound_impact;
+		world.insert_entity(this);
+	}
+
+	void tick()
+	{
+		vec2 oldpos = pos;
+		vel.y += 0.25f;
+		pos += vel;
+		lifespan--;
+		// check player intersection as well
+		entity* targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner);
+		if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y))
+		{
+			if (lifespan >= 0)
+				create_sound(pos, sound_impact);
+			if (flags & PROJECTILE_FLAGS_EXPLODE)
+			{
+				create_explosion(oldpos, owner);
+			}
+			else if (targetplayer)
+			{
+				targetplayer->take_damage(normalize(vel) * force, damage, owner);
+			}
+			world.destroy_entity(this);
+		}
+	}
+	
+	void snap(int snapping_client)
+	{
+		obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile));
+		proj->x = (int)pos.x;
+		proj->y = (int)pos.y;
+		proj->vx = (int)vel.x;
+		proj->vy = (int)vel.y;
+		proj->type = type;
+	}
+};
+
+// player entity
+class player : public entity
+{
+public:
+	static const int phys_size = 28;
+	enum
+	{
+		WEAPON_NEEDRELOAD		= 1 << 0,
+		WEAPON_DROPONUNEQUIP	= 1 << 1,
+		WEAPON_DRAWSAMMO		= 1 << 2,
+		WEAPON_HASSECONDARY		= 1 << 3,
+		WEAPON_ISACTIVE			= 1 << 4, // has the item
+		WEAPON_AUTOFIRE			= 1 << 5,
+
+		WEAPON_PROJECTILETYPE_GUN		= 0,
+		WEAPON_PROJECTILETYPE_ROCKET	= 1,
+		WEAPON_PROJECTILETYPE_SHOTGUN	= 2,
+
+		// Gun
+
+
+		// modifiers
+		MODIFIER_HASACTIVATIONS			= 1 << 0,
+		MODIFIER_TIMELIMITED			= 1 << 1,
+		MODIFIER_ISACTIVE				= 1 << 2,
+		MODIFIER_NEEDSACTIVATION		= 1 << 3,
+
+		MODIFIER_RETURNFLAGS_OVERRIDEWEAPON		= 1 << 0,
+		MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY	= 1 << 1,
+		MODIFIER_RETURNFLAGS_OVERRIDEPOSITION	= 1 << 2,
+		MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY	= 1 << 3,
+	};
+	class weapon
+	{
+	public:
+		entity* hitobjects[10];
+		int numobjectshit; // for melee, so we don't hit the same object more than once per bash
+		int weapontype;
+		int equiptime;
+		int unequiptime;
+		int numammo;
+		int magsize;
+		int nummagazines;
+		int flags;
+		int firetime;
+		int reloadtime;
+		int projectileclass;
+		int damage;
+		int sound_fire;
+		int sound_equip;
+		int sound_impact;
+		int sound_impact_projectile;
+		int visualtimeattack;
+		float projectilevel;
+		float projectilespan;
+		float reach; // for melee
+		float force;
+		float recoilforce;
+		float projoffsety;
+		float projoffsetx;
+		
+		weapon()
+		{
+			weapontype = 0;
+			numammo = 0;
+			flags = 0;
+			reloadtime = 0;
+			projectileclass = 0;
+			numobjectshit = 0;
+			reach = 0.0f;
+			force = 5.0f;
+			damage = 1;
+			sound_fire = -1;
+			sound_equip = -1;
+			sound_impact = -1;
+			sound_impact_projectile = -1,
+			visualtimeattack = 3;
+			recoilforce = 0.0f;
+			projoffsety = 0.0f;
+			projoffsetx = 0.0f;
+		}
+
+		void setgun(int ammo = 10)
+		{
+			weapontype = WEAPON_TYPE_GUN;
+			flags = 0;//WEAPON_DRAWSAMMO;
+			numammo = ammo;
+			projectileclass = WEAPON_PROJECTILETYPE_GUN;
+			firetime = SERVER_TICK_SPEED/10;
+			magsize = 0;
+			projectilevel = 30.0f;
+			projectilespan = 50.0f * 1.0f;
+			sound_fire = SOUND_FIRE_GUN;
+			sound_equip = SOUND_EQUIP_GUN;
+			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_GUN;
+			projoffsety = -10.0f;
+			projoffsetx = 24.0f;
+		}
+
+		void setrocket(int ammo = 10)
+		{
+			weapontype = WEAPON_TYPE_ROCKET;
+			flags = WEAPON_DRAWSAMMO;
+			numammo = ammo;
+			projectileclass = WEAPON_PROJECTILETYPE_ROCKET;
+			projectilevel = 15.0f;
+			projectilespan = 50.0f * 5.0f;
+			firetime = SERVER_TICK_SPEED * 4/5;
+			magsize = 0;
+			recoilforce = 5.0f;
+			sound_fire = SOUND_FIRE_ROCKET;
+			sound_equip = SOUND_EQUIP_ROCKET;
+			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_ROCKET;
+			projoffsety = -17.0f;
+			projoffsetx = 24.0f;
+		}
+
+		/*void setsniper(int ammo = 10)
+		{
+			weapontype = WEAPON_TYPE_SNIPER;
+			flags = WEAPON_DRAWSAMMO | WEAPON_HASSECONDARY | WEAPON_NEEDRELOAD;
+			numammo = ammo;
+			projectileclass = WEAPON_PROJECTILETYPE_SNIPER;
+			projectilevel = 30.0f;
+			projectilespan = 50.0f * 5.0f;
+			firetime = SERVER_TICK_SPEED;
+			reloadtime = SERVER_TICK_SPEED/2;
+			magsize = 2;
+			recoilforce = 20.0f;
+		}*/
+
+		void setshotgun(int ammo = 10)
+		{
+			weapontype = WEAPON_TYPE_SHOTGUN;
+			flags = WEAPON_DRAWSAMMO | WEAPON_NEEDRELOAD;
+			numammo = ammo;
+			projectileclass = WEAPON_PROJECTILETYPE_SHOTGUN;
+			projectilevel = 30.0f;
+			projectilespan = 50.0f * 5.0f;
+			firetime = SERVER_TICK_SPEED/2;
+			reloadtime = SERVER_TICK_SPEED/2;
+			magsize = 2;
+			damage = 3;
+			recoilforce = 5.0f;
+			sound_fire = SOUND_FIRE_SHOTGUN;
+			sound_equip = SOUND_EQUIP_SHOTGUN;
+			sound_impact_projectile = SOUND_IMPACT_PROJECTILE_SHOTGUN;
+			projoffsety = -17.0f;
+			projoffsetx = 24.0f;
+		}
+
+		void setmelee(int ammo = 10)
+		{
+			weapontype = WEAPON_TYPE_MELEE;
+			flags = 0;//WEAPON_AUTOFIRE;
+			numammo = ammo;
+			projectileclass = -1;
+			firetime = SERVER_TICK_SPEED/5;
+			reloadtime = 0;
+			magsize = 2;
+			numobjectshit = 0;
+			reach = 15.0f;
+			damage = 1;
+			sound_fire = SOUND_FIRE_MELEE;
+			sound_equip = SOUND_EQUIP_MELEE;
+			sound_impact = SOUND_PLAYER_IMPACT;
+		}
+
+		void settype()
+		{
+			switch(weapontype)
+			{
+			case WEAPON_TYPE_GUN:
+				{
+					setgun();
+					break;
+				}
+			case WEAPON_TYPE_ROCKET:
+				{
+					setrocket();
+					break;
+				}
+			/*case WEAPON_TYPE_SNIPER:
+				{
+					setsniper();
+					break;
+				}*/
+			case WEAPON_TYPE_SHOTGUN:
+				{
+					setshotgun();
+					break;
+				}
+			case WEAPON_TYPE_MELEE:
+				{
+					setmelee();
+					break;
+				}
+			default:
+				break;
+			}
+		}
+
+		int activate(player* player)
+		{
+			// create sound event for fire
+			int projectileflags = 0;
+			create_sound(player->pos, sound_fire);
+
+			switch (weapontype)
+			{
+			case WEAPON_TYPE_ROCKET:
+				projectileflags |= projectile::PROJECTILE_FLAGS_EXPLODE;
+			case WEAPON_TYPE_GUN:
+			//case WEAPON_TYPE_SNIPER:
+			case WEAPON_TYPE_SHOTGUN:
+				{
+					if (flags & WEAPON_DRAWSAMMO)
+						numammo--;
+					// Create projectile
+					new projectile(projectileclass, player->client_id, player->pos+vec2(0,projoffsety)+player->direction*projoffsetx, player->direction*projectilevel, projectilespan, player, damage, projectileflags, force, sound_impact_projectile);
+					// recoil force if any
+					if (recoilforce > 0.0f)
+					{
+						vec2 dir(player->direction.x,0.5);
+						if (dir.x == 0.0f)
+							dir.x = 0.5f;
+						else
+							dir = normalize(dir);
+						player->vel -= dir * recoilforce;
+					}
+					return firetime;
+				}
+			case WEAPON_TYPE_MELEE:
+				{
+					// Start bash sequence
+					numobjectshit = 0;
+					return firetime;
+				}
+			default:
+				return 0;
+			}
+		}
+
+		void update(player* owner, int fire_timeout)
+		{
+			switch(weapontype)
+			{
+			case WEAPON_TYPE_MELEE:
+				{
+					// No more melee
+					if (fire_timeout <= 0)
+						return;
+
+					// only one that needs update (for now)
+					// do selection for the weapon and bash anything in it
+					// check if we hit anything along the way
+					int type = OBJTYPE_PLAYER;
+					entity *ents[64];
+					vec2 dir = owner->pos + owner->direction * reach;
+					float radius = length(dir * 0.5f);
+					vec2 center = owner->pos + dir * 0.5f;
+					int num = world.find_entities(center, radius, ents, 64, &type, 1);
+					
+					for (int i = 0; i < num; i++)
+					{
+						// Check if entity is a player
+						if (ents[i] == owner)
+							continue;
+						// make sure we haven't hit this object before
+						bool balreadyhit = false;
+						for (int j = 0; j < numobjectshit; j++)
+						{
+							if (hitobjects[j] == ents[i])
+								balreadyhit = true;
+						}
+						if (balreadyhit)
+							continue;
+
+						// check so we are sufficiently close
+						if (distance(ents[i]->pos, owner->pos) > (owner->phys_size * 2.0f))
+							continue;
+					
+						// hit a player, give him damage and stuffs...
+						// create sound for bash
+						create_sound(ents[i]->pos, sound_impact);
+
+						// set his velocity to fast upward (for now)
+						create_smoke(ents[i]->pos);
+						hitobjects[numobjectshit++] = ents[i];
+						ents[i]->take_damage(vec2(0,10.0f), damage, owner->client_id);
+						player* target = (player*)ents[i];
+						vec2 dir;
+						if (length(target->pos - owner->pos) > 0.0f)
+							dir = normalize(target->pos - owner->pos);
+						else
+							dir = vec2(0,-1);
+						target->vel += dir * 10.0f + vec2(0,-10.0f);
+					}
+					break;
+				}
+			default:
+				break;
+			}
+		}
+	};
+
+	class modifier
+	{
+	public:
+		vec2 activationdir;
+		entity* hitobjects[10];
+		int numobjectshit;
+		float velocity;
+		int modifiertype;
+		int duration;
+		int numactivations;
+		int activationtime;
+		int cooldown;
+		int movetime;
+		int visualtimeattack;
+		int currentactivation;
+		int currentmovetime;
+		int currentcooldown;
+		int damage;
+		int flags;
+		int sound_impact;
+		int sound_activate;
+
+		modifier()
+		{
+			modifiertype = 0;
+			duration = 0;
+			numobjectshit = 0;
+			numactivations = 0;
+			activationtime = 0;
+			cooldown = 0;
+			movetime = 0;
+			currentactivation = 0;
+			currentmovetime = 0;
+			currentcooldown =0;
+			damage = 0;
+			flags = 0;
+			activationdir = vec2(0.0f, 1.0f);
+			velocity = 0.0f;
+			visualtimeattack = 0;
+			sound_impact = -1;
+		}
+
+		void setninja()
+		{
+			modifiertype = MODIFIER_TYPE_NINJA;
+			duration = SERVER_TICK_SPEED * 15;
+			numactivations = -1;
+			movetime = SERVER_TICK_SPEED / 5;
+			activationtime = SERVER_TICK_SPEED / 2;
+			cooldown = SERVER_TICK_SPEED;
+			currentactivation = 0;
+			currentmovetime = 0;
+			numobjectshit = 0;
+			damage = 3;
+			flags = MODIFIER_TIMELIMITED | MODIFIER_NEEDSACTIVATION;
+			velocity = 50.0f;
+			visualtimeattack = 3;
+			sound_impact = SOUND_PLAYER_IMPACT_NINJA;
+			sound_activate = SOUND_FIRE_NINJA;
+		}
+
+		void settimefield()
+		{
+			modifiertype = MODIFIER_TYPE_TIMEFIELD;
+			duration = SERVER_TICK_SPEED * 10;
+			numactivations = -1;
+			activationtime = SERVER_TICK_SPEED;
+			numobjectshit = 0;
+			currentactivation = 0;
+			flags = MODIFIER_TIMELIMITED;
+			velocity = 0.0f;
+		}
+
+		void settype()
+		{
+			switch (modifiertype)
+			{
+			case MODIFIER_TYPE_NINJA:
+				{
+					setninja();
+					break;
+				}
+			case MODIFIER_TYPE_TIMEFIELD:
+				{
+					settimefield();
+					break;
+				}
+			default:
+				break;
+			}
+		}
+
+		int updateninja(player* player)
+		{
+			if (currentactivation <= 0)
+				return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
+			currentactivation--;
+			currentmovetime--;
+			
+			if (currentmovetime == 0)
+			{	
+				// reset player velocity
+				player->vel *= 0.2f;
+				//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
+			}
+			
+			if (currentmovetime > 0)
+			{
+				// Set player velocity
+				player->vel = activationdir * velocity;
+				vec2 oldpos = player->pos;
+				move_box(&player->pos, &player->vel, vec2(player->phys_size, player->phys_size), 0.0f);
+				// reset velocity so the client doesn't predict stuff
+				player->vel = vec2(0.0f,0.0f);
+				if ((currentmovetime % 2) == 0)
+				{
+					create_smoke(player->pos);
+				}
+				
+				// check if we hit anything along the way
+				{
+					int type = OBJTYPE_PLAYER;
+					entity *ents[64];
+					vec2 dir = player->pos - oldpos;
+					float radius = length(dir * 0.5f);
+					vec2 center = oldpos + dir * 0.5f;
+					int num = world.find_entities(center, radius, ents, 64, &type, 1);
+					
+					for (int i = 0; i < num; i++)
+					{
+						// Check if entity is a player
+						if (ents[i] == player)
+							continue;
+						// make sure we haven't hit this object before
+						bool balreadyhit = false;
+						for (int j = 0; j < numobjectshit; j++)
+						{
+							if (hitobjects[j] == ents[i])
+								balreadyhit = true;
+						}
+						if (balreadyhit)
+							continue;
+
+						// check so we are sufficiently close
+						if (distance(ents[i]->pos, player->pos) > (player->phys_size * 2.0f))
+							continue;
+					
+						// hit a player, give him damage and stuffs...
+						create_sound(ents[i]->pos, sound_impact);
+						// set his velocity to fast upward (for now)
+						hitobjects[numobjectshit++] = ents[i];
+						ents[i]->take_damage(vec2(0,10.0f), damage, player->client_id);
+					}
+				}
+				return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION|MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY;
+			}
+
+
+			// move char, and check intersection from us to target
+			return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY;
+		}
+
+		int activateninja(player* player)
+		{
+			// ok then, activate ninja
+			activationdir = player->direction;
+			currentactivation = activationtime;
+			currentmovetime = movetime;
+			currentcooldown = cooldown;
+			// reset hit objects
+			numobjectshit = 0;
+
+			create_sound(player->pos, SOUND_FIRE_NINJA);
+
+			return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
+		}
+
+		int activate(player* player)
+		{
+			if (flags & MODIFIER_NEEDSACTIVATION)
+			{
+				if (!numactivations)
+					return 0;
+				numactivations--;
+			}
+
+			switch (modifiertype)
+			{
+			case MODIFIER_TYPE_NINJA:
+				{
+					return activateninja(player);
+				}
+			/*case MODIFIER_TYPE_TIMEFIELD:
+				{
+					updatetimefield();
+					break;
+				}*/
+			default:
+				return 0;
+			}
+		}
+		int update(player* player)
+		{
+			switch (modifiertype)
+			{
+			case MODIFIER_TYPE_NINJA:
+				{
+					return updateninja(player);
+				}
+			/*case MODIFIER_TYPE_TIMEFIELD:
+				{
+					updatetimefield();
+					break;
+				}*/
+			default:
+				return 0;
+			}
+		}
+	};
+
+	enum
+	{
+		PLAYER_FLAGS_ISRELOADING = 1 << 0,
+		PLAYER_FLAGS_ISEQUIPPING = 1 << 1,
+	};
+
+	weapon lweapons[WEAPON_NUMWEAPONS];
+	modifier modifiers[MODIFIER_NUMMODIFIERS];
+	int iactiveweapon;
+	int inextweapon;
+	int equip_time;
+
+	int client_id;
+	int flags;
+
+	char name[32];
+	player_input previnput;
+	player_input input;
+	int tick_count;
+	int damage_taken_tick;
+
+	vec2 vel;
+	vec2 direction;
+
+	int jumped;
+	int airjumped;
+
+	//int firing;
+	int hooking;
+
+	int fire_timeout;
+	int reload_timeout;
+
+	int health;
+	int armor;
+
+	int score;
+
+	// sounds
+	int sound_player_jump;
+	int sound_player_land;
+	int sound_player_hurt_short;
+	int sound_player_hurt_long;
+	int sound_player_spawn;
+	int sound_player_chain_loop;
+	int sound_player_chain_impact;
+	int sound_player_impact;
+	int sound_player_impact_ninja;
+	int sound_player_die;
+	int sound_player_switchweapon;
+
+	player* phookedplayer;
+	powerup* phookedpowerup;
+	int numhooked;
+	vec2 hook_pos;
+	vec2 hook_dir;
+
+	player() :
+		entity(OBJTYPE_PLAYER)
+	{
+		reset();
+		
+		//firing = 0;
+		// setup weaponflags and stuff
+		lweapons[WEAPON_TYPE_GUN].setgun();
+		lweapons[WEAPON_TYPE_ROCKET].setrocket();
+		//lweapons[WEAPON_TYPE_SNIPER].setsniper();
+		lweapons[WEAPON_TYPE_SHOTGUN].setshotgun();
+		lweapons[WEAPON_TYPE_MELEE].setmelee();
+
+		modifiers[MODIFIER_TYPE_NINJA].setninja();
+		modifiers[MODIFIER_TYPE_TIMEFIELD].settimefield();
+		//modifiers[MODIFIER_TYPE_NINJA].flags |= MODIFIER_ISACTIVE;
+
+		sound_player_jump = SOUND_PLAYER_JUMP;
+		sound_player_hurt_short = SOUND_PLAYER_HURT_SHORT;
+		sound_player_hurt_long = SOUND_PLAYER_HURT_LONG;
+		sound_player_spawn = SOUND_PLAYER_SPAWN;
+		sound_player_chain_loop = SOUND_PLAYER_CHAIN_LOOP;
+		sound_player_chain_impact = SOUND_PLAYER_CHAIN_IMPACT;
+		sound_player_impact = SOUND_PLAYER_IMPACT;
+		sound_player_impact_ninja = SOUND_PLAYER_IMPACT_NINJA;
+		sound_player_die = SOUND_PLAYER_DIE;
+		sound_player_switchweapon = SOUND_PLAYER_SWITCHWEAPON;
+		sound_player_land = SOUND_PLAYER_LAND;
+	}
+	
+	void reset()
+	{
+		equip_time = 0;
+		phookedplayer = 0;
+		numhooked = 0;
+		proximity_radius = phys_size;
+		name[0] = 'n';
+		name[1] = 'o';
+		name[2] = 'o';
+		name[3] = 'b';
+		name[4] = 0;
+		
+		pos = vec2(100.0f, 0.0f);
+		vel = vec2(0.0f, 0.0f);
+		direction = vec2(0.0f, 1.0f);
+		client_id = -1;
+		tick_count = 0;
+		score = 0;
+		flags = 0;		
+	}
+	
+	virtual void destroy() { flags = 0; }
+		
+	void respawn()
+	{
+		health = PLAYER_MAXHEALTH;
+		armor = 0;
+		
+		hooking = 0;
+		phookedplayer = 0;
+		phookedpowerup = 0;
+		numhooked = 0;
+		fire_timeout = 0;
+		reload_timeout = 0;
+		iactiveweapon = 0;
+		inextweapon = -1;
+		equip_time = 0;
+		jumped = 0;
+		airjumped = 0;
+		mem_zero(&input, sizeof(input));
+		vel = vec2(0.0f, 0.0f);
+		
+		int start, num;
+		map_get_type(1, &start, &num);
+		
+		if(num)
+		{
+			mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL);
+			pos = vec2(sp->x, sp->y);
+		}
+		else
+			pos = vec2(100.0f, -60.0f);
+
+		// reset active flags
+		for (int i = 0; i < WEAPON_NUMWEAPONS; i++)
+		{
+			// reset and remove
+			lweapons[i].settype();
+			lweapons[i].flags &= ~WEAPON_ISACTIVE;
+		}
+
+
+		// TEMP REMOVE
+		
+		/*for (int i = 0; i < WEAPON_NUMWEAPONS; i++)
+		{
+			lweapons[i].settype();
+			lweapons[i].flags |= WEAPON_ISACTIVE;
+		}*/
+		lweapons[WEAPON_TYPE_MELEE].flags |= WEAPON_ISACTIVE;
+		// Add gun as default weapon
+		iactiveweapon = WEAPON_TYPE_GUN;
+		lweapons[WEAPON_TYPE_GUN].numammo = 10;
+		lweapons[WEAPON_TYPE_GUN].flags |= WEAPON_ISACTIVE;
+
+		create_sound(pos, sound_player_spawn);
+	}
+	
+	bool is_grounded()
+	{
+		if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5)))
+			return true;
+		if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5)))
+			return true;
+		return false;
+	}
+
+	// Disable weapon activation if this returns true
+	int handlemodifiers()
+	{
+		int returnflags = 0;
+		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
+		{
+			if (modifiers[i].flags & MODIFIER_ISACTIVE)
+			{
+				modifiers[i].duration--;
+				modifiers[i].currentcooldown--;
+
+				// Check if it should activate
+				if (modifiers[i].currentcooldown <= 0 && 
+					(modifiers[i].flags & MODIFIER_NEEDSACTIVATION) &&
+					input.fire && !(previnput.fire))
+				{
+					returnflags |= modifiers[i].activate(this);
+				}
+
+				returnflags |= modifiers[i].update(this);
+
+				// remove active if timed out
+				if (modifiers[i].duration <= 0 && modifiers[i].currentactivation <= 0)
+					modifiers[i].flags &= ~MODIFIER_ISACTIVE;
+			}
+		}
+		return returnflags;
+	}
+
+	void handleweapon()
+	{
+		// handle weapon
+		if(input.fire && (!previnput.fire || lweapons[iactiveweapon].flags & WEAPON_AUTOFIRE) && 
+			!(flags & PLAYER_FLAGS_ISEQUIPPING) && !reload_timeout)
+		{
+			if(fire_timeout == 0)
+			{
+				if (lweapons[iactiveweapon].numammo || !(lweapons[iactiveweapon].flags & WEAPON_DRAWSAMMO))
+				{
+					// Decrease ammo
+					fire_timeout = lweapons[iactiveweapon].activate(this);
+				}
+				else if ((lweapons[iactiveweapon].flags & WEAPON_NEEDRELOAD) && lweapons[iactiveweapon].nummagazines)
+				{
+					// reload
+					reload_timeout = lweapons[iactiveweapon].reloadtime;
+					lweapons[iactiveweapon].nummagazines--;
+					lweapons[iactiveweapon].numammo = lweapons[iactiveweapon].magsize;
+				}
+			}
+		}
+
+		// update active weapon
+		lweapons[iactiveweapon].update(this, fire_timeout);
+	}
+
+	void handlehook()
+	{
+		// handle hook
+		if(input.hook)
+		{
+			if(hooking == 0)
+			{
+				hooking = 1;
+				hook_pos = pos;
+				hook_dir = direction;
+				// Sound
+				create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STARTLOOP);
+			}
+			else if(hooking == 1)
+			{
+				vec2 new_pos = hook_pos+hook_dir*hook_fire_speed;
+
+				// Check against other players and powerups first
+				player* targetplayer = 0;
+				powerup* targetpowerup = 0;
+				{
+					static const int typelist[2] = { OBJTYPE_PLAYER, OBJTYPE_POWERUP};
+					entity *ents[64];
+					vec2 dir = new_pos - hook_pos;
+					float radius = length(dir * 0.5f);
+					vec2 center = hook_pos + dir * 0.5f;
+					int num = world.find_entities(center, radius, ents, 64,typelist,2);
+					
+					for (int i = 0; i < num; i++)
+					{
+						// Check if entity is a player
+						if (ents[i] == this || (targetplayer && targetpowerup))
+							continue;
+
+						if (!targetplayer && ents[i]->objtype == OBJTYPE_PLAYER)
+						{
+							// temp, set hook pos to our position
+							if (((player*)ents[i])->phookedplayer != this)
+								targetplayer = (player*)ents[i];
+						}
+						else if (!targetpowerup && ents[i]->objtype == OBJTYPE_POWERUP && 
+							(((powerup*)ents[i])->flags & powerup::POWERUP_FLAG_HOOKABLE))
+						{
+							targetpowerup = (powerup*)ents[i];
+						}
+					}
+				}
+
+				//player* targetplayer = intersect_player(hook_pos, hook_pos, new_pos, this);
+				if (targetplayer)
+				{
+					// So he can't move "normally"
+					new_pos = targetplayer->pos;
+					phookedplayer = targetplayer;
+					targetplayer->numhooked++;
+					hooking = 3;
+					create_sound(pos, sound_player_chain_impact);
+
+					// stop looping chain sound
+					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
+				}
+				else if (targetpowerup)
+				{
+					new_pos = targetpowerup->pos;
+					phookedpowerup = targetpowerup;
+					phookedpowerup->playerhooked = this;
+					hooking = 4;
+					create_sound(pos, sound_player_chain_impact);
+
+					// stop looping chain sound
+					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
+				}
+				else if(intersect_line(hook_pos, new_pos, &new_pos))
+				{
+					hooking = 2;
+					create_sound(pos, sound_player_chain_impact);
+
+					// stop looping chain sound
+					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
+				}
+				else if(distance(pos, new_pos) > hook_length)
+				{
+					hooking = -1;
+					create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP);
+				}
+				
+				hook_pos = new_pos;
+			}
+			else if(hooking == 2)
+			{
+				vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel;
+				// the hook as more power to drag you up then down.
+				// this makes it easier to get on top of an platform
+				if(hookvel.y > 0)
+					hookvel.y *= 0.3f;
+				
+				// the hook will boost it's power if the player wants to move
+				// in that direction. otherwise it will dampen everything abit
+				if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) 
+					hookvel.x *= 0.95f;
+				else
+					hookvel.x *= 0.75f;
+				vec2 new_vel = vel+hookvel;
+				
+				// check if we are under the legal limit for the hook
+				if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
+					vel = new_vel; // no problem. apply
+			}
+			else if (hooking == 3)
+			{
+				// hmm, force the targetplayer towards us if possible, otherwise us towards them if they are already hooked
+				if (phookedplayer)
+				{
+					if (phookedplayer->hooking > 1)
+					{
+						// Drag us towards target player
+						vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel;
+						if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) 
+							hookvel.x *= 0.95f;
+						else
+							hookvel.x *= 0.75f;
+
+						// Apply the velocity
+						// the hook will boost it's power if the player wants to move
+						// in that direction. otherwise it will dampen everything abit
+						vec2 new_vel = vel+hookvel;
+						
+						// check if we are under the legal limit for the hook
+						if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
+							vel = new_vel; // no problem. apply
+					}
+					else
+					{
+						// Drag targetplayer towards us
+						vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel;
+
+						// Apply the velocity
+						// the hook will boost it's power if the player wants to move
+						// in that direction. otherwise it will dampen everything abit
+						vec2 new_vel = phookedplayer->vel+hookvel;
+						
+						// check if we are under the legal limit for the hook
+						if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
+							phookedplayer->vel = new_vel; // no problem. apply
+					}
+					hook_pos = phookedplayer->pos;
+					// if hooked player dies, release the hook
+				}
+				else
+				{
+					hooking = -1;
+					phookedplayer = 0;
+				}
+			}
+			else if (hooking == 4)
+			{
+				// Have a powerup, drag it towards us
+				vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel;
+
+				// Apply the velocity
+				// the hook will boost it's power if the player wants to move
+				// in that direction. otherwise it will dampen everything abit
+				vec2 new_vel = phookedpowerup->vel+hookvel;
+				
+				// check if we are under the legal limit for the hook
+				if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
+					phookedpowerup->vel = new_vel; // no problem. apply
+				hook_pos = phookedpowerup->pos;
+			}
+		}
+		else
+		{
+			hooking = 0;
+			hook_pos = pos;
+			if (phookedplayer)
+			{
+				phookedplayer->numhooked--;
+				phookedplayer = 0;
+			}
+		}
+	}
+
+	void getattackticks(int& curattack, int& attacklen, int& visualtimeattack)
+	{
+		// time left from current attack (if any)
+		// first check modifiers (ninja...)
+		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
+		{
+			if ((modifiers[i].flags & (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) == (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION))
+			{
+				curattack = modifiers[i].currentactivation;
+				attacklen = modifiers[i].activationtime;
+				visualtimeattack = modifiers[i].visualtimeattack;
+				return;
+			}
+		}
+
+		// otherwise current fire timeout
+		curattack = fire_timeout;
+		attacklen = lweapons[iactiveweapon].firetime;
+		visualtimeattack = lweapons[iactiveweapon].visualtimeattack;
+	}
+	
+	virtual void tick()
+	{
+		tick_count++;
+		
+		// fetch some info
+		bool grounded = is_grounded();
+		direction = get_direction(input.angle);
+		
+		// decrease reload timer
+		if(fire_timeout)
+			fire_timeout--;
+		if (reload_timeout)
+			reload_timeout--;
+
+		// Switch weapons
+		if (flags & PLAYER_FLAGS_ISEQUIPPING)
+		{
+			equip_time--;
+			if (equip_time <= 0)
+			{
+				if (inextweapon >= 0)
+				{
+					equip_time = SERVER_TICK_SPEED * lweapons[inextweapon].equiptime;
+					iactiveweapon = inextweapon;
+					inextweapon = -1;
+
+					// Send switch weapon event to client?
+				}
+				else
+				{
+					flags &= ~PLAYER_FLAGS_ISEQUIPPING;
+				}
+			}
+		}
+		else if (input.activeweapon && iactiveweapon != (input.activeweapon & ~0x80000000))
+		{
+			input.activeweapon &= ~0x80000000;
+			// check which weapon to activate
+			if (input.activeweapon >= 0 && input.activeweapon < WEAPON_NUMWEAPONS && 
+				(lweapons[input.activeweapon].flags & WEAPON_ISACTIVE))
+			{
+				inextweapon = input.activeweapon;
+				equip_time = SERVER_TICK_SPEED * lweapons[iactiveweapon].unequiptime;
+				// unequip current
+				flags |= PLAYER_FLAGS_ISEQUIPPING;
+
+				create_sound(pos, sound_player_switchweapon);
+			}
+		}
+
+		// don't do any weapon activations if modifier is currently overriding
+		int modifierflags = handlemodifiers();
+		if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEWEAPON))
+			handleweapon();
+
+		handlehook();
+		
+		// handle movement
+		if(grounded)
+		{
+			if (airjumped)
+				create_sound(pos, SOUND_PLAYER_LAND);
+			airjumped = 0;
+		}
+		
+		float elast = 0.0f;
+
+		if (!numhooked)
+		{
+			// I'm hooked by someone, so don't do any movement plz (temp)
+			if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY))
+			{
+				if(grounded)
+				{
+					// ground movement
+					if(input.left)
+					{
+						if(vel.x > -ground_control_speed)
+						{
+							vel.x -= ground_control_accel; 
+							if(vel.x < -ground_control_speed)
+								vel.x = -ground_control_speed;
+						}
+					}
+					else if(input.right)
+					{
+						if(vel.x < ground_control_speed)
+						{
+							vel.x += ground_control_accel;
+							if(vel.x > ground_control_speed)
+								vel.x = ground_control_speed;
+						}
+					}
+					else
+						vel.x *= ground_friction; // ground fiction
+				}
+				else
+				{
+					// air movement
+					if(input.left)
+					{
+						if(vel.x > -air_control_speed)
+							vel.x -= air_control_accel;
+					}
+					else if(input.right)
+					{
+						if(vel.x < air_control_speed)
+							vel.x += air_control_accel;
+					}
+					else
+						vel.x *= air_friction; // air fiction
+				}
+				
+				if(input.jump)
+				{
+					if(jumped == 0)
+					{
+						if(grounded)
+						{
+							create_sound(pos, sound_player_jump);
+							vel.y = -ground_jump_speed;
+							jumped++;
+						}
+						/*
+						else if(airjumped == 0)
+						{
+							vel.y = -12;
+							airjumped++;
+							jumped++;
+						}*/
+					}
+					else if (!grounded)
+					{
+						airjumped++;
+					}
+				}
+				else
+					jumped = 0;
+			}
+				
+			// meh, go through all players and stop their hook on me
+			/*
+			for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
+			{
+				if (ent && ent->objtype == OBJTYPE_PLAYER)
+				{
+					player *p = (player*)ent;
+					if(p != this)
+					{
+						float d = distance(pos, p->pos);
+						vec2 dir = normalize(pos - p->pos);
+						if(d < phys_size*1.5f)
+						{
+							float a = phys_size*1.5f - d;
+							vel = vel + dir*a;
+						}
+						
+						if(p->phookedplayer == this)
+						{
+							if(d > phys_size*2.5f)
+							{
+								elast = 0.0f;
+								vel = vel - dir*2.5f;
+							}
+						}
+					}
+				}
+			}*/
+			
+			// gravity
+			if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY))
+				vel.y += gravity;
+		}
+		
+		if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEPOSITION))
+			move_box(&pos, &vel, vec2(phys_size, phys_size), elast);
+	}
+	
+	void die()
+	{
+		create_sound(pos, sound_player_die);
+		// release our hooked player
+		if (phookedplayer)
+		{
+			phookedplayer->numhooked--;
+			phookedplayer = 0;
+			hooking = -1;
+			numhooked = 0;
+		}
+		respawn();
+
+		// meh, go through all players and stop their hook on me
+		for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
+		{
+			if (ent && ent->objtype == OBJTYPE_PLAYER)
+			{
+				player* p = (player*)ent;
+				if (p->phookedplayer == this)
+				{
+					p->phookedplayer = 0;
+					p->hooking = -1;
+					//p->numhooked--;
+				}
+			}
+		}
+	}
+	
+	virtual bool take_damage(vec2 force, int dmg, int from)
+	{
+		vel += force;
+
+		if(armor)
+		{
+			armor -= 1;
+			dmg--;
+		}
+		
+		if(dmg > armor)
+		{
+			dmg -= armor;
+			armor = 0;
+			health -= dmg;
+		}
+		else
+			armor -= dmg;
+		/*
+		int armordmg = (dmg+1)/2;
+		int healthdmg = dmg-armordmg;
+		if(armor < armordmg)
+		{
+			healthdmg += armordmg - armor;
+			armor = 0;
+		}
+		else
+			armor -= armordmg;
+			
+		health -= healthdmg;
+		*/
+		
+		// create healthmod indicator
+		create_healthmod(pos, dmg);
+		
+		damage_taken_tick = tick_count+50;
+		
+		// check for death
+		if(health <= 0)
+		{
+			// apply score
+			if(from != -1)
+			{
+				if(from == client_id)
+					score--;
+				else
+				{
+					player *p = get_player(from);
+					p->score++;
+				}
+			}
+			
+			die();
+			return false;
+		}
+
+		if (dmg > 2)
+			create_sound(pos, sound_player_hurt_long);
+		else
+			create_sound(pos, sound_player_hurt_short);
+
+		// spawn blood?
+
+		return true;
+	}
+
+	virtual void snap(int snaping_client)
+	{
+		obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player));
+
+		client_info info;
+		if(server_getclientinfo(client_id, &info))
+			snap_encode_string(info.name, player->name, strlen(info.name), 32);
+		
+		player->x = (int)pos.x;
+		player->y = (int)pos.y;
+		player->vx = (int)vel.x;
+		player->vy = (int)vel.y;
+		player->emote = EMOTE_NORMAL;
+
+		player->ammocount = lweapons[iactiveweapon].numammo;
+		player->health = 0;
+		player->armor = 0;
+		player->local = 0;
+		player->clientid = client_id;
+		player->weapon = iactiveweapon;
+		player->modifier = 0;
+		for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++)
+		{
+			// add active modifiers
+			if (modifiers[i].flags & MODIFIER_ISACTIVE)
+				player->modifier |= 1 << i;
+		}
+		// get current attack ticks
+		getattackticks(player->attackticks, player->attacklen, player->visualtimeattack);
+		
+
+		if(client_id == snaping_client)
+		{
+			player->local = 1;
+			player->health = health;
+			player->armor = armor;
+		}
+		
+		if(length(vel) > 15.0f)
+			player->emote = EMOTE_HAPPY;
+		
+		if(damage_taken_tick > tick_count)
+			player->emote = EMOTE_PAIN;
+		
+		if(player->emote == EMOTE_NORMAL)
+		{
+			if((tick_count%(50*5)) < 10)
+				player->emote = EMOTE_BLINK;
+		}
+		
+		player->hook_active = hooking>0?1:0;
+		player->hook_x = (int)hook_pos.x;
+		player->hook_y = (int)hook_pos.y;
+			
+		player->angle = input.angle;
+		player->score = score;
+	}
+};
+
+// POWERUP ///////////////////////
+
+powerup::powerup(int _type, int _subtype, int _numitems, int _flags) : 
+	entity(OBJTYPE_POWERUP)
+{
+	static int current_id = 0;
+	playerhooked = 0;
+	id = current_id++;
+	vel = vec2(0.0f,0.0f);
+	type = _type;
+	subtype = _subtype;
+	numitems = _numitems;
+	flags = _flags;
+	// set radius (so it can collide and be hooked and stuff)
+	proximity_radius = phys_size;
+	spawntick = -1;
+	world.insert_entity(this);
+}
+
+void powerup::spawnrandom(int _lifespan)
+{
+	return;
+	/*
+	vec2 pos;
+	int start, num;
+	map_get_type(1, &start, &num);
+	
+	if(!num)
+		return;
+	
+	mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL);
+	pos = vec2(sp->x, sp->y);
+
+	// Check if there already is a powerup at that location
+	{
+		int type = OBJTYPE_POWERUP;
+		entity *ents[64];
+		int num = world.find_entities(pos, 5.0f, ents, 64,&type,1);
+		for (int i = 0; i < num; i++)
+		{
+			if (ents[i]->objtype == OBJTYPE_POWERUP)
+			{
+				// location busy
+				return;
+			}
+		}
+	}
+
+	powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan);
+	ppower->pos = pos;
+	if (ppower->type == POWERUP_TYPE_WEAPON)
+	{
+		ppower->subtype = rand() % WEAPON_NUMWEAPONS;
+		ppower->numitems = 10;
+	}
+	else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR)
+	{
+		ppower->numitems = rand() % 5;
+	}
+	ppower->flags |= POWERUP_FLAG_HOOKABLE;*/
+}
+
+void powerup::tick()
+{
+	// wait for respawn
+	if(spawntick > 0)
+	{
+		if(server_tick() > spawntick)
+			spawntick = -1;
+		else
+			return;
+	}
+	
+	vec2 oldpos = pos;
+	//vel.y += 0.25f;
+	pos += vel;
+	move_box(&pos, &vel, vec2(phys_size, phys_size), 0.0f);
+
+	// Check if a player intersected us
+	vec2 meh;
+	player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0);
+	if (pplayer)
+	{
+		// player picked us up, is someone was hooking us, let them go
+		if (playerhooked)
+			playerhooked->hooking = -1;
+		int respawntime = -1;
+		switch (type)
+		{
+		case POWERUP_TYPE_HEALTH:
+			{
+				if(pplayer->health < PLAYER_MAXHEALTH)
+				{
+					pplayer->health = min(PLAYER_MAXHEALTH, pplayer->health + numitems);
+					respawntime = 20;
+				}
+				break;
+			}
+		case POWERUP_TYPE_ARMOR:
+			{
+				if(pplayer->armor < PLAYER_MAXARMOR)
+				{
+					pplayer->armor = min(PLAYER_MAXARMOR, pplayer->armor + numitems);
+					respawntime = 20;
+				}
+				break;
+			}
+		case POWERUP_TYPE_WEAPON:
+			{
+				if (pplayer->lweapons[subtype].flags & player::WEAPON_ISACTIVE)
+				{
+					// add ammo
+					/*
+					if (pplayer->lweapons[subtype].flags & player::WEAPON_NEEDRELOAD)
+					{
+						int numtoadd = min(numitems, pplayer->lweapons[subtype].magsize);
+						pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numtoadd);
+						pplayer->lweapons[subtype].nummagazines += (numitems - numtoadd) % pplayer->lweapons[subtype].magsize;
+					}
+					else*/
+					if(pplayer->lweapons[subtype].numammo < 10)
+					{
+						respawntime = 20;
+						pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numitems);
+					}
+				}
+				else
+				{
+					pplayer->lweapons[subtype].settype();
+					pplayer->lweapons[subtype].flags |= player::WEAPON_ISACTIVE;
+					respawntime = 20;
+				}
+				break;
+			}
+		case POWERUP_TYPE_NINJA:
+			{
+				respawntime = 60;
+				// reset and activate
+				pplayer->modifiers[MODIFIER_TYPE_NINJA].settype();
+				pplayer->modifiers[MODIFIER_TYPE_NINJA].flags |= player::MODIFIER_ISACTIVE;
+				break;
+			}
+		//POWERUP_TYPE_TIMEFIELD		= 4,
+		default:
+			break;
+		};
+		
+		if(respawntime >= 0)
+			spawntick = server_tick() + server_tickspeed() * respawntime;
+		//world.destroy_entity(this);
+	}
+}
+
+void powerup::snap(int snapping_client)
+{
+	if(spawntick != -1)
+		return;
+
+	obj_powerup *powerup = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup));
+	powerup->x = (int)pos.x;
+	powerup->y = (int)pos.y;
+	powerup->vx = (int)vel.x;
+	powerup->vy = (int)vel.y;
+	powerup->type = type;
+	powerup->subtype = subtype;
+}
+
+// POWERUP END ///////////////////////
+
+static player players[MAX_CLIENTS];
+
+player *get_player(int index)
+{
+	return &players[index];
+}
+
+void create_healthmod(vec2 p, int amount)
+{
+	ev_healthmod *ev = (ev_healthmod *)events.create(EVENT_HEALTHMOD, sizeof(ev_healthmod));
+	ev->x = (int)p.x;
+	ev->y = (int)p.y;
+	ev->amount = amount;
+}
+
+void create_explosion(vec2 p, int owner, bool bnodamage)
+{
+	// create the event
+	ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion));
+	ev->x = (int)p.x;
+	ev->y = (int)p.y;
+	
+	if (!bnodamage)
+	{
+		// deal damage
+		entity *ents[64];
+		const float radius = 128.0f;
+		int num = world.find_entities(p, radius, ents, 64);
+		for(int i = 0; i < num; i++)
+		{
+			vec2 diff = ents[i]->pos - p;
+			vec2 forcedir(0,1);
+			if (length(diff))
+				forcedir = normalize(diff);
+			float l = length(diff);
+			float dmg = 5 * (1 - (l/radius));
+			if((int)dmg)
+			{
+				ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);/* && 
+						ents[i]->objtype == OBJTYPE_PLAYER &&
+						owner >= 0)
+				{
+					player *p = (player*)ents[i];
+					if(p->client_id == owner)
+						p->score--;
+					else
+						((player*)ents[owner])->score++;
+
+				}*/
+			}
+		}
+	}
+}
+
+void create_smoke(vec2 p)
+{
+	// create the event
+	ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion));
+	ev->x = (int)p.x;
+	ev->y = (int)p.y;
+}
+
+void create_sound(vec2 pos, int sound, int loopingflags)
+{
+	if (sound < 0)
+		return;
+
+	// create a sound
+	ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND, sizeof(ev_sound));
+	ev->x = (int)pos.x;
+	ev->y = (int)pos.y;
+	ev->sound = sound | loopingflags;
+}
+
+player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
+{
+	// Find other players
+	entity *ents[64];
+	vec2 dir = pos1 - pos0;
+	float radius = length(dir * 0.5f);
+	vec2 center = pos0 + dir * 0.5f;
+	int num = world.find_entities(center, radius, ents, 64);
+	for (int i = 0; i < num; i++)
+	{
+		// Check if entity is a player
+		if (ents[i] != notthis && ents[i]->objtype == OBJTYPE_PLAYER)
+		{
+			// temp, set hook pos to our position
+			new_pos = ents[i]->pos;
+			return (player*)ents[i];
+		}
+	}
+
+	return 0;
+}
+
+// Server hooks
+static int addtick = SERVER_TICK_SPEED * 5;
+void mods_tick()
+{
+	// clear all events
+	events.clear();
+	world.tick();
+
+	if (addtick <= 0)
+	{
+		powerup::spawnrandom(SERVER_TICK_SPEED * 5);
+		addtick = SERVER_TICK_SPEED * 5;
+	}
+	addtick--;
+}
+
+void mods_snap(int client_id)
+{
+	world.snap(client_id);
+	events.snap(client_id);
+}
+
+void mods_client_input(int client_id, void *input)
+{
+	players[client_id].previnput = players[client_id].input;
+	players[client_id].input = *(player_input*)input;
+}
+
+void mods_client_enter(int client_id)
+{
+	players[client_id].reset();
+	players[client_id].client_id = client_id;
+	players[client_id].respawn();
+	world.insert_entity(&players[client_id]);
+	
+}
+
+void mods_client_drop(int client_id)
+{
+	players[client_id].client_id = -1;
+	world.remove_entity(&players[client_id]);
+}
+
+void mods_init()
+{
+	col_init(32);
+
+	int start, num;
+	map_get_type(MAPRES_ITEM, &start, &num);
+	
+	for(int i = 0; i < num; i++)
+	{
+		mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0);
+		
+		int type = -1;
+		int subtype = -1;
+		int numitems = 1;
+		
+		switch(it->type)
+		{
+		case ITEM_WEAPON_GUN:
+			type = POWERUP_TYPE_WEAPON;
+			subtype = WEAPON_TYPE_GUN;
+			break;
+		case ITEM_WEAPON_SHOTGUN:
+			type = POWERUP_TYPE_WEAPON;
+			subtype = WEAPON_TYPE_SHOTGUN;
+			numitems = 5;
+			break;
+		case ITEM_WEAPON_ROCKET:
+			type = POWERUP_TYPE_WEAPON;
+			subtype = WEAPON_TYPE_ROCKET;
+			numitems = 5;
+			break;
+		/*case ITEM_WEAPON_SNIPER:
+			type = POWERUP_TYPE_WEAPON;
+			subtype = WEAPON_TYPE_ROCKET;
+			break;*/
+		case ITEM_WEAPON_HAMMER:
+			type = POWERUP_TYPE_WEAPON;
+			subtype = WEAPON_TYPE_MELEE;
+			break;
+		
+		case ITEM_HEALTH_1:
+			type = POWERUP_TYPE_HEALTH;
+			numitems = 1;
+			break;
+		case ITEM_HEALTH_5:
+			type = POWERUP_TYPE_HEALTH;
+			numitems = 5;
+			break;
+		case ITEM_HEALTH_10:
+			type = POWERUP_TYPE_HEALTH;
+			numitems = 10;
+			break;
+		
+		case ITEM_ARMOR_1:
+			type = POWERUP_TYPE_ARMOR;
+			numitems = 1;
+			break;
+		case ITEM_ARMOR_5:
+			type = POWERUP_TYPE_ARMOR;
+			numitems = 5;
+			break;
+		case ITEM_ARMOR_10:
+			type = POWERUP_TYPE_ARMOR;
+			numitems = 10;
+			break;
+		};
+		
+		powerup* ppower = new powerup(type, subtype, numitems);
+		ppower->pos.x = it->x;
+		ppower->pos.y = it->y;
+	}
+		
+	
+	/*
+	powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan);
+	ppower->pos = pos;
+	if (ppower->type == POWERUP_TYPE_WEAPON)
+	{
+		ppower->subtype = rand() % WEAPON_NUMWEAPONS;
+		ppower->numitems = 10;
+	}
+	else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR)
+	{
+		ppower->numitems = rand() % 5;
+	}
+	ppower->flags |= POWERUP_FLAG_HOOKABLE;
+	*/
+	
+	//powerup::spawnrandom(SERVER_TICK_SPEED * 5);
+}
+
+void mods_shutdown() {}
+void mods_presnap() {}
+void mods_postsnap() {}
diff --git a/src/game/mapres.h b/src/game/mapres.h
new file mode 100644
index 00000000..8d09e99c
--- /dev/null
+++ b/src/game/mapres.h
@@ -0,0 +1,7 @@
+enum
+{
+	MAPRES_REGISTERED=0x8000,
+	MAPRES_IMAGE=0x8001,
+	MAPRES_TILEMAP=0x8002,
+	MAPRES_COLLISIONMAP=0x8003,
+};
diff --git a/src/game/mapres_col.cpp b/src/game/mapres_col.cpp
new file mode 100644
index 00000000..0cf71986
--- /dev/null
+++ b/src/game/mapres_col.cpp
@@ -0,0 +1,44 @@
+#include <baselib/system.h>
+#include "../interface.h"
+#include "mapres_col.h"
+#include "mapres.h"
+
+/*
+	Simple collision rutines!
+*/
+struct collision
+{
+	int w, h;
+	unsigned char *data;
+};
+
+static collision col;
+static int global_dividor;
+
+int col_init(int dividor)
+{
+	mapres_collision *c = (mapres_collision*)map_find_item(MAPRES_COLLISIONMAP,0);
+	if(!c)
+	{
+		dbg_msg("mapres_col", "failed!");
+		return 0;
+	}
+	col.w = c->width;
+	col.h = c->height;
+	global_dividor = dividor;
+	col.data = (unsigned char *)map_get_data(c->data_index);
+	return col.data ? 1 : 0;
+}
+
+int col_check_point(int x, int y)
+{
+	int nx = x/global_dividor;
+	int ny = y/global_dividor;
+	if(nx < 0 || nx >= col.w || ny >= col.h)
+		return 1;
+	
+	if(y < 0)
+		return 0; // up == sky == free
+	
+	return col.data[ny*col.w+nx];
+}
diff --git a/src/game/mapres_col.h b/src/game/mapres_col.h
new file mode 100644
index 00000000..2afad439
--- /dev/null
+++ b/src/game/mapres_col.h
@@ -0,0 +1,10 @@
+
+struct mapres_collision
+{
+	int width;
+	int height;
+	int data_index;
+};
+
+int col_init(int dividor);
+int col_check_point(int x, int y);
diff --git a/src/game/mapres_image.cpp b/src/game/mapres_image.cpp
new file mode 100644
index 00000000..baf7f09b
--- /dev/null
+++ b/src/game/mapres_image.cpp
@@ -0,0 +1,41 @@
+#include <baselib/system.h>
+#include "../interface.h"
+#include "mapres_image.h"
+#include "mapres.h"
+
+static int map_textures[64] = {0};
+static int count = 0;
+
+int img_init()
+{
+	int start, count;
+	map_get_type(MAPRES_IMAGE, &start, &count);
+	dbg_msg("mapres_image", "start=%d count=%d", start, count);
+	for(int i = 0; i < 64; i++)
+	{
+		if(map_textures[i])
+		{
+			gfx_unload_texture(map_textures[i]);
+			map_textures[i] = 0;
+		}
+	}
+
+	for(int i = 0; i < count; i++)
+	{
+		mapres_image *img = (mapres_image *)map_get_item(start+i, 0, 0);
+		void *data = map_get_data(img->image_data);
+		map_textures[i] = gfx_load_texture_raw(img->width, img->height, data);
+	}
+	
+	return count;
+}
+
+int img_num()
+{
+	return count;
+}
+
+int img_get(int index)
+{
+	return map_textures[index];
+}
diff --git a/src/game/mapres_image.h b/src/game/mapres_image.h
new file mode 100644
index 00000000..eab1559a
--- /dev/null
+++ b/src/game/mapres_image.h
@@ -0,0 +1,18 @@
+
+// loads images from the map to textures
+int img_init();
+
+// returns the number of images in the map
+int img_num();
+
+// fetches the texture id for the image
+int img_get(int index);
+
+
+class mapres_image
+{
+public:
+	int width;
+	int height;
+	int image_data;
+};
diff --git a/src/game/mapres_tilemap.cpp b/src/game/mapres_tilemap.cpp
new file mode 100644
index 00000000..0868d2e4
--- /dev/null
+++ b/src/game/mapres_tilemap.cpp
@@ -0,0 +1,54 @@
+#include "../interface.h"
+#include "mapres_tilemap.h"
+#include "mapres_image.h"
+#include "mapres.h"
+
+int tilemap_init()
+{
+	return 0;
+}
+
+void tilemap_render(float scale, int fg)
+{
+	if(!map_is_loaded())
+		return;
+	
+	// fetch indecies
+	int start, num;
+	map_get_type(MAPRES_TILEMAP, &start, &num);
+
+	// render tilemaps
+	int passed_main = 0;
+	for(int t = 0; t < num; t++)
+	{
+		mapres_tilemap *tmap = (mapres_tilemap *)map_get_item(start+t,0,0);
+		unsigned char *data = (unsigned char *)map_get_data(tmap->data);
+		
+		if(tmap->main)
+			passed_main = 1;
+
+		if((fg && passed_main) || (!fg && !passed_main))
+		{
+			gfx_texture_set(img_get(tmap->image));
+			gfx_quads_begin();
+			
+			int c = 0;
+			float frac = (1.0f/1024.0f); //2.0f;
+			for(int y = 0; y < tmap->height; y++)
+				for(int x = 0; x < tmap->width; x++, c++)
+				{
+					unsigned char d = data[c*2];
+					if(d)
+					{
+						gfx_quads_setsubset(
+							(d%16)/16.0f+frac,
+							(d/16)/16.0f+frac,
+							(d%16)/16.0f+1.0f/16.0f-frac,
+							(d/16)/16.0f+1.0f/16.0f-frac);
+						gfx_quads_drawTL(x*scale, y*scale, scale, scale);
+					}
+				}
+			gfx_quads_end();
+		}
+	}
+}
diff --git a/src/game/mapres_tilemap.h b/src/game/mapres_tilemap.h
new file mode 100644
index 00000000..6e9d81be
--- /dev/null
+++ b/src/game/mapres_tilemap.h
@@ -0,0 +1,19 @@
+
+// dependencies: image
+
+//
+int tilemap_init();
+
+// renders the tilemaps
+void tilemap_render(float scale, int fg);
+
+struct mapres_tilemap
+{
+	int image;
+	int width;
+	int height;
+	int x, y;
+	int scale;
+	int data;
+	int main;
+};
diff --git a/src/gfx.cpp b/src/gfx.cpp
new file mode 100644
index 00000000..c1e05c9c
--- /dev/null
+++ b/src/gfx.cpp
@@ -0,0 +1,581 @@
+#include <baselib/opengl.h>
+#include <baselib/vmath.h>
+#include <baselib/stream/file.h>
+
+#include "interface.h"
+
+using namespace baselib;
+
+static opengl::context context;
+
+struct custom_vertex
+{
+	vec3 pos;
+	vec2 tex;
+	vec4 color;
+};
+
+const int vertexBufferSize = 2048;
+//static custom_vertex vertices[4];
+static custom_vertex* g_pVertices = 0;
+static int g_iVertexStart = 0;
+static int g_iVertexEnd = 0;
+static vec4 g_QuadColor[4];
+static vec2 g_QuadTexture[4];
+
+static opengl::vertex_buffer vertex_buffer;
+//static int screen_width = 800;
+//static int screen_height = 600;
+static int screen_width = 1024;
+static int screen_height = 768;
+static float rotation = 0;
+static int quads_drawing = 0;
+
+
+struct texture_holder
+{
+	opengl::texture tex;
+	int flags;
+	int next;
+};
+
+static const int MAX_TEXTURES = 128;
+
+static texture_holder textures[MAX_TEXTURES];
+static int first_free_texture;
+
+static const char null_texture_data[] = {
+	0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, 
+	0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff, 
+	0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, 
+	0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff, 
+};
+
+static void draw_quad(bool _bflush = false)
+{
+	if (!_bflush && ((g_iVertexEnd + 4) < vertexBufferSize))
+	{
+		// Just add
+		g_iVertexEnd += 4;
+	}
+	else if (g_iVertexEnd)
+	{
+		if (!_bflush)
+			g_iVertexEnd += 4;
+		if(GLEW_VERSION_2_0)
+		{
+			// set the data
+			vertex_buffer.data(g_pVertices, vertexBufferSize * sizeof(custom_vertex), GL_DYNAMIC_DRAW);
+			opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0);
+			opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT,
+					sizeof(custom_vertex),
+					sizeof(vec3));
+			opengl::stream_color(&vertex_buffer, 4, GL_FLOAT,
+					sizeof(custom_vertex),
+					sizeof(vec3)+sizeof(vec2));		
+			opengl::draw_arrays(GL_QUADS, 0, g_iVertexEnd);
+		}
+		else
+		{
+			glVertexPointer(3, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices);
+			glTexCoordPointer(2, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices + sizeof(vec3));
+			glColorPointer(4, GL_FLOAT,
+					sizeof(custom_vertex),
+					(char*)g_pVertices + sizeof(vec3) + sizeof(vec2));
+			glEnableClientState(GL_VERTEX_ARRAY);
+			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+			glEnableClientState(GL_COLOR_ARRAY);
+			glDrawArrays(GL_QUADS, 0, g_iVertexEnd);
+		}
+		// Reset pointer
+		g_iVertexEnd = 0;
+	}
+}
+	
+bool gfx_init(bool fullscreen)
+{
+	if(!context.create(screen_width, screen_height, 24, 8, 16, 0, fullscreen?opengl::context::FLAG_FULLSCREEN:0))
+	{
+		dbg_msg("game", "failed to create gl context");
+		return false;
+	}
+	// Init vertices
+	if (g_pVertices)
+		mem_free(g_pVertices);
+	g_pVertices = (custom_vertex*)mem_alloc(sizeof(custom_vertex) * vertexBufferSize, 1);
+	g_iVertexStart = 0;
+	g_iVertexEnd = 0;
+
+	context.set_title("---");
+
+	/*
+	dbg_msg("gfx", "OpenGL version %d.%d.%d", context.version_major(),
+											  context.version_minor(),
+											  context.version_rev());*/
+
+	gfx_mapscreen(0,0,screen_width, screen_height);
+	
+	// TODO: make wrappers for this
+	glEnable(GL_BLEND);
+	
+	// model
+	mat4 mat = mat4::identity;
+	opengl::matrix_modelview(&mat);
+	
+	// Set all z to -5.0f
+	for (int i = 0; i < vertexBufferSize; i++)
+		g_pVertices[i].pos.z = -5.0f;
+
+	if(GLEW_VERSION_2_0)
+	{
+		// set the streams
+		vertex_buffer.data(g_pVertices, sizeof(vertexBufferSize), GL_DYNAMIC_DRAW);
+		opengl::stream_vertex(&vertex_buffer, 3, GL_FLOAT, sizeof(custom_vertex), 0);
+		opengl::stream_texcoord(&vertex_buffer, 0, 2, GL_FLOAT,
+				sizeof(custom_vertex),
+				sizeof(vec3));
+		opengl::stream_color(&vertex_buffer, 4, GL_FLOAT,
+				sizeof(custom_vertex),
+				sizeof(vec3)+sizeof(vec2));		
+	}
+
+	// init textures
+	first_free_texture = 0;
+	for(int i = 0; i < MAX_TEXTURES; i++)
+		textures[i].next = i+1;
+	textures[MAX_TEXTURES-1].next = -1;
+	
+	// create null texture, will get id=0
+	gfx_load_texture_raw(4,4,null_texture_data);
+	
+	return true;
+}
+
+int gfx_unload_texture(int index)
+{
+	textures[index].tex.clear();
+	textures[index].next = first_free_texture;
+	first_free_texture = index;
+	return 0;
+}
+
+void gfx_blend_normal()
+{
+	// TODO: wrapper for this
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void gfx_blend_additive()
+{
+	// TODO: wrapper for this
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+}
+
+int gfx_load_texture_raw(int w, int h, const void *data)
+{
+	// grab texture
+	int tex = first_free_texture;
+	first_free_texture = textures[tex].next;
+	textures[tex].next = -1;
+	
+	// set data and return
+	// TODO: should be RGBA, not BGRA
+	dbg_msg("gfx", "%d = %dx%d", tex, w, h);
+	textures[tex].tex.data2d(w, h, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, data);
+	return tex;
+}
+
+// simple uncompressed RGBA loaders
+int gfx_load_texture_tga(const char *filename)
+{
+	image_info img;
+	
+	if(gfx_load_tga(&img, filename))
+	{
+		int id = gfx_load_texture_raw(img.width, img.height, img.data);
+		mem_free(img.data);
+		return id;
+	}
+	
+	return 0;
+}
+
+int gfx_load_tga(image_info *img, const char *filename)
+{
+	// open file for reading
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("game/tga", "failed to open file. filename='%s'", filename);
+		return 0;
+	}
+	
+	// read header
+	unsigned char headers[18];
+	file.read(headers, sizeof(headers));
+	img->width = headers[12]+(headers[13]<<8);
+	img->height = headers[14]+(headers[15]<<8);
+
+	bool flipx = (headers[17] >> 4) & 1;
+	bool flipy = !((headers[17] >> 5) & 1);
+
+	if(headers[2] != 2) // needs to be uncompressed RGB
+	{
+		dbg_msg("game/tga", "tga not uncompressed rgb. filename='%s'", filename);
+		return 0;
+	}
+		
+	if(headers[16] != 32) // needs to be RGBA
+	{
+		dbg_msg("game/tga", "tga is 32bit. filename='%s'", filename);
+		return 0;
+	}
+	
+	// read data
+	int data_size = img->width*img->height*4;
+	img->data = mem_alloc(data_size, 1);
+
+	if (flipy)
+	{
+		for (int i = 0; i < img->height; i++)
+		{
+			file.read((char *)img->data + (img->height-i-1)*img->width*4, img->width*4);
+		}
+	}
+	else
+		file.read(img->data, data_size);
+	file.close();
+
+	return 1;
+}
+
+void gfx_shutdown()
+{
+	if (g_pVertices)
+		mem_free(g_pVertices);
+	context.destroy();
+}
+
+void gfx_swap()
+{
+	context.swap();
+}
+
+int gfx_screenwidth()
+{
+	return screen_width;
+}
+
+int gfx_screenheight()
+{
+	return screen_height;
+}
+
+void gfx_texture_set(int slot)
+{
+	dbg_assert(quads_drawing == 0, "called gfx_texture_set within quads_begin");
+	if(slot == -1)
+		opengl::texture_disable(0);
+	else
+		opengl::texture_2d(0, &textures[slot].tex);
+}
+
+void gfx_clear(float r, float g, float b)
+{
+	glClearColor(r,g,b,1.0f);
+	opengl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y)
+{
+	mat4 mat;
+	mat.ortho(tl_x, br_x, br_y, tl_y, 1.0f, 10.f);
+	opengl::matrix_projection(&mat);
+}
+
+void gfx_setoffset(float x, float y)
+{
+	//const float scale = 0.75f;
+	const float scale = 1.0f;
+	mat4 mat = mat4::identity;
+	mat.m[0] = scale;
+	mat.m[5] = scale;
+	mat.m[10] = scale;
+	mat.m[12] = x*scale;
+	mat.m[13] = y*scale;
+	opengl::matrix_modelview(&mat);
+}
+
+void gfx_quads_begin()
+{
+	dbg_assert(quads_drawing == 0, "called quads_begin twice");
+	quads_drawing++;
+	
+	gfx_quads_setsubset(0,0,1,1);
+	gfx_quads_setrotation(0);
+	gfx_quads_setcolor(1,1,1,1);
+}
+
+void gfx_quads_end()
+{
+	dbg_assert(quads_drawing == 1, "called quads_end without quads_begin");
+	draw_quad(true);
+	quads_drawing--;
+}
+
+
+void gfx_quads_setrotation(float angle)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setrotation without quads_begin");
+	rotation = angle;
+}
+
+void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolorvertex without quads_begin");
+	g_QuadColor[i].r = r;
+	g_QuadColor[i].g = g;
+	g_QuadColor[i].b = b;
+	g_QuadColor[i].a = a;
+}
+
+void gfx_quads_setcolor(float r, float g, float b, float a)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setcolor without quads_begin");
+	g_QuadColor[0] = vec4(r,g,b,a);
+	g_QuadColor[1] = vec4(r,g,b,a);
+	g_QuadColor[2] = vec4(r,g,b,a);
+	g_QuadColor[3] = vec4(r,g,b,a);
+	/*gfx_quads_setcolorvertex(0,r,g,b,a);
+	gfx_quads_setcolorvertex(1,r,g,b,a);
+	gfx_quads_setcolorvertex(2,r,g,b,a);
+	gfx_quads_setcolorvertex(3,r,g,b,a);*/
+}
+
+void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v)
+{
+	dbg_assert(quads_drawing == 1, "called gfx_quads_setsubset without quads_begin");
+
+	g_QuadTexture[0].x = tl_u;
+	g_QuadTexture[0].y = tl_v;
+	//g_pVertices[g_iVertexEnd].tex.u = tl_u;
+	//g_pVertices[g_iVertexEnd].tex.v = tl_v;
+
+	g_QuadTexture[1].x = br_u;
+	g_QuadTexture[1].y = tl_v;
+	//g_pVertices[g_iVertexEnd + 2].tex.u = br_u;
+	//g_pVertices[g_iVertexEnd + 2].tex.v = tl_v;
+
+	g_QuadTexture[2].x = br_u;
+	g_QuadTexture[2].y = br_v;
+	//g_pVertices[g_iVertexEnd + 1].tex.u = tl_u;
+	//g_pVertices[g_iVertexEnd + 1].tex.v = br_v;
+
+	g_QuadTexture[3].x = tl_u;
+	g_QuadTexture[3].y = br_v;
+	//g_pVertices[g_iVertexEnd + 3].tex.u = br_u;
+	//g_pVertices[g_iVertexEnd + 3].tex.v = br_v;
+}
+
+static void rotate(vec3 &center, vec3 &point)
+{
+	vec3 p = point-center;
+	point.x = p.x * cosf(rotation) - p.y * sinf(rotation) + center.x;
+	point.y = p.x * sinf(rotation) + p.y * cosf(rotation) + center.y;
+}
+
+void gfx_quads_draw(float x, float y, float w, float h)
+{
+	gfx_quads_drawTL(x-w/2, y-h/2,w,h);
+}
+
+void gfx_quads_drawTL(float x, float y, float width, float height)
+{
+	dbg_assert(quads_drawing == 1, "called quads_draw without quads_begin");
+	
+	vec3 center;
+	center.x = x + width/2;
+	center.y = y + height/2;
+	center.z = 0;
+	
+	g_pVertices[g_iVertexEnd].pos.x = x;
+	g_pVertices[g_iVertexEnd].pos.y = y;
+	g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x;
+	g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y;
+	g_pVertices[g_iVertexEnd].color = g_QuadColor[0];
+	rotate(center, g_pVertices[g_iVertexEnd].pos);
+
+	g_pVertices[g_iVertexEnd + 1].pos.x = x+width;
+	g_pVertices[g_iVertexEnd + 1].pos.y = y;
+	g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x;
+	g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y;
+	g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1];
+	rotate(center, g_pVertices[g_iVertexEnd + 1].pos);
+
+	g_pVertices[g_iVertexEnd + 2].pos.x = x + width;
+	g_pVertices[g_iVertexEnd + 2].pos.y = y+height;
+	g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x;
+	g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y;
+	g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2];
+	rotate(center, g_pVertices[g_iVertexEnd + 2].pos);
+
+	g_pVertices[g_iVertexEnd + 3].pos.x = x;
+	g_pVertices[g_iVertexEnd + 3].pos.y = y+height;
+	g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x;
+	g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y;
+	g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3];
+	rotate(center, g_pVertices[g_iVertexEnd + 3].pos);
+	
+	draw_quad();
+}
+
+void gfx_quads_draw_freeform(
+	float x0, float y0,
+	float x1, float y1,
+	float x2, float y2,
+	float x3, float y3)
+{
+	dbg_assert(quads_drawing == 1, "called quads_draw_freeform without quads_begin");
+	
+	g_pVertices[g_iVertexEnd].pos.x = x0;
+	g_pVertices[g_iVertexEnd].pos.y = y0;
+	g_pVertices[g_iVertexEnd].tex.u = g_QuadTexture[0].x;
+	g_pVertices[g_iVertexEnd].tex.v = g_QuadTexture[0].y;
+	g_pVertices[g_iVertexEnd].color = g_QuadColor[0];
+
+	g_pVertices[g_iVertexEnd + 1].pos.x = x1;
+	g_pVertices[g_iVertexEnd + 1].pos.y = y1;
+	g_pVertices[g_iVertexEnd + 1].tex.u = g_QuadTexture[1].x;
+	g_pVertices[g_iVertexEnd + 1].tex.v = g_QuadTexture[1].y;
+	g_pVertices[g_iVertexEnd + 1].color = g_QuadColor[1];
+
+	g_pVertices[g_iVertexEnd + 2].pos.x = x3;
+	g_pVertices[g_iVertexEnd + 2].pos.y = y3;
+	g_pVertices[g_iVertexEnd + 2].tex.u = g_QuadTexture[2].x;
+	g_pVertices[g_iVertexEnd + 2].tex.v = g_QuadTexture[2].y;
+	g_pVertices[g_iVertexEnd + 2].color = g_QuadColor[2];
+
+	g_pVertices[g_iVertexEnd + 3].pos.x = x2;
+	g_pVertices[g_iVertexEnd + 3].pos.y = y2;
+	g_pVertices[g_iVertexEnd + 3].tex.u = g_QuadTexture[3].x;
+	g_pVertices[g_iVertexEnd + 3].tex.v = g_QuadTexture[3].y;
+	g_pVertices[g_iVertexEnd + 3].color = g_QuadColor[3];
+	
+	draw_quad();
+}
+
+void gfx_quads_text(float x, float y, float size, const char *text)
+{
+	gfx_quads_begin();
+	while(*text)
+	{
+		char c = *text;
+		text++;
+		
+		gfx_quads_setsubset(
+			(c%16)/16.0f,
+			(c/16)/16.0f,
+			(c%16)/16.0f+1.0f/16.0f,
+			(c/16)/16.0f+1.0f/16.0f);
+		
+		gfx_quads_drawTL(x,y,size,size);
+		x += size/2;
+	}
+	
+	gfx_quads_end();
+}
+
+struct pretty_font
+{
+	float m_CharStartTable[256];
+	float m_CharEndTable[256];
+	int font_texture;
+};
+
+pretty_font default_font = 
+{
+{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0.421875, 0.359375, 0.265625, 0.25, 0.1875, 0.25, 0.4375, 0.390625, 0.390625, 0.34375, 0.28125, 0.421875, 0.390625, 0.4375, 0.203125, 
+        0.265625, 0.28125, 0.28125, 0.265625, 0.25, 0.28125, 0.28125, 0.265625, 0.28125, 0.265625, 0.4375, 0.421875, 0.3125, 0.28125, 0.3125, 0.3125, 
+        0.25, 0.234375, 0.28125, 0.265625, 0.265625, 0.296875, 0.3125, 0.25, 0.25, 0.421875, 0.28125, 0.265625, 0.328125, 0.171875, 0.234375, 0.25, 
+        0.28125, 0.234375, 0.265625, 0.265625, 0.28125, 0.265625, 0.234375, 0.09375, 0.234375, 0.234375, 0.265625, 0.390625, 0.203125, 0.390625, 0.296875, 0.28125, 
+        0.375, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.359375, 0.296875, 0.3125, 0.4375, 0.390625, 0.328125, 0.4375, 0.203125, 0.3125, 0.296875, 
+        0.3125, 0.296875, 0.359375, 0.3125, 0.328125, 0.3125, 0.296875, 0.203125, 0.296875, 0.296875, 0.328125, 0.375, 0.421875, 0.375, 0.28125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 
+        0, 0.421875, 0.3125, 0.265625, 0.25, 0.25, 0.421875, 0.265625, 0.375, 0.21875, 0.375, 0.328125, 0.3125, 0, 0.21875, 0.28125, 
+        0.359375, 0.28125, 0.34375, 0.34375, 0.421875, 0.3125, 0.265625, 0.421875, 0.421875, 0.34375, 0.375, 0.328125, 0.125, 0.125, 0.125, 0.296875, 
+        0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.234375, 0.109375, 0.265625, 0.296875, 0.296875, 0.296875, 0.296875, 0.375, 0.421875, 0.359375, 0.390625, 
+        0.21875, 0.234375, 0.25, 0.25, 0.25, 0.25, 0.25, 0.296875, 0.21875, 0.265625, 0.265625, 0.265625, 0.265625, 0.234375, 0.28125, 0.3125, 
+        0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.1875, 0.3125, 0.3125, 0.3125, 0.3125, 0.3125, 0.375, 0.421875, 0.359375, 0.390625, 
+        0.3125, 0.3125, 0.296875, 0.296875, 0.296875, 0.296875, 0.296875, 0.28125, 0.28125, 0.3125, 0.3125, 0.3125, 0.3125, 0.296875, 0.3125, 0.296875, 
+},
+{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+        0.2, 0.5625, 0.625, 0.71875, 0.734375, 0.796875, 0.765625, 0.546875, 0.59375, 0.59375, 0.65625, 0.703125, 0.546875, 0.59375, 0.5625, 0.6875, 
+        0.71875, 0.609375, 0.703125, 0.703125, 0.71875, 0.703125, 0.703125, 0.6875, 0.703125, 0.703125, 0.5625, 0.546875, 0.671875, 0.703125, 0.671875, 0.671875, 
+        0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.6875, 0.6875, 0.734375, 0.71875, 0.5625, 0.65625, 0.765625, 0.703125, 0.8125, 0.75, 0.734375, 
+        0.734375, 0.765625, 0.71875, 0.71875, 0.703125, 0.71875, 0.75, 0.890625, 0.75, 0.75, 0.71875, 0.59375, 0.6875, 0.59375, 0.6875, 0.703125, 
+        0.5625, 0.671875, 0.6875, 0.671875, 0.671875, 0.671875, 0.625, 0.671875, 0.671875, 0.5625, 0.546875, 0.703125, 0.5625, 0.78125, 0.671875, 0.671875, 
+        0.6875, 0.671875, 0.65625, 0.671875, 0.65625, 0.671875, 0.6875, 0.78125, 0.6875, 0.671875, 0.65625, 0.609375, 0.546875, 0.609375, 0.703125, 0.671875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 
+        0, 0.5625, 0.671875, 0.734375, 0.734375, 0.734375, 0.546875, 0.71875, 0.609375, 0.765625, 0.609375, 0.65625, 0.671875, 0, 0.765625, 0.703125, 
+        0.625, 0.703125, 0.640625, 0.640625, 0.609375, 0.671875, 0.703125, 0.546875, 0.5625, 0.578125, 0.609375, 0.65625, 0.859375, 0.859375, 0.859375, 0.671875, 
+        0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.84375, 0.734375, 0.6875, 0.6875, 0.6875, 0.6875, 0.5625, 0.609375, 0.640625, 0.59375, 
+        0.734375, 0.75, 0.734375, 0.734375, 0.734375, 0.734375, 0.734375, 0.6875, 0.765625, 0.71875, 0.71875, 0.71875, 0.71875, 0.75, 0.734375, 0.6875, 
+        0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.796875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.5625, 0.609375, 0.625, 0.59375, 
+        0.6875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.703125, 0.703125, 0.671875, 0.671875, 0.671875, 0.671875, 0.671875, 0.6875, 0.671875, 
+},
+0
+};
+
+pretty_font *current_font = &default_font;
+
+void gfx_pretty_text(float x, float y, float size, const char *text)
+{
+	const float spacing = 0.05f;
+	
+	gfx_quads_begin();
+	
+	while (*text)
+	{
+		const char c = *text;
+		const float width = current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c];
+		
+		text++;
+
+		gfx_quads_setsubset(
+			(c%16)/16.0f + current_font->m_CharStartTable[c]/16.0f, // startx
+			(c/16)/16.0f, // starty
+			(c%16)/16.0f + current_font->m_CharEndTable[c]/16.0f + 0.001, // endx
+			(c/16)/16.0f+1.0f/16.0f); // endy
+
+		gfx_quads_drawTL(x, y, width * size, size);
+
+		x += (width + spacing) * size;
+	}
+
+	gfx_quads_end();
+}
+
+float gfx_pretty_text_width(float size, const char *text)
+{
+	const float spacing = 0.05f;
+	float width = 0.0f;
+
+	while (*text)
+	{
+		const char c = *text++;
+		width += size * (current_font->m_CharEndTable[c] - current_font->m_CharStartTable[c] + spacing);
+	}
+
+	return width;
+}
diff --git a/src/interface.h b/src/interface.h
new file mode 100644
index 00000000..46d7ede7
--- /dev/null
+++ b/src/interface.h
@@ -0,0 +1,711 @@
+#ifndef FILE_INTERFACE_H
+#define FILE_INTERFACE_H
+
+/*
+	Title: Engine Interface
+*/
+
+// TODO: Move the definitions of these keys here
+#include <baselib/keys.h>
+
+enum
+{
+	MAX_CLIENTS=8,
+	SERVER_TICK_SPEED=50,
+	SERVER_CLIENT_TIMEOUT=5,
+	SNAP_CURRENT=0,
+	SNAP_PREV=1,
+};
+
+struct snap_item
+{
+	int type;
+	int id;
+};
+
+struct client_info
+{
+public:
+	const char *name;
+	int latency;
+};
+
+struct image_info
+{
+	int width, height;
+	void *data;
+};
+
+int gfx_load_tga(image_info *img, const char *filename);
+
+
+/*
+	Group: Graphics
+*/
+
+// graphics
+bool gfx_init(bool fullscreen); // NOT EXPOSED
+void gfx_shutdown(); // NOT EXPOSED
+void gfx_swap(); // NOT EXPOSED
+
+// textures
+/*
+	Function: gfx_load_texture_tga
+		Loads a TGA from file.
+	
+	Arguments:
+		filename - Null terminated string to the file to load.
+	
+	Returns:
+		An ID to the texture. -1 on failure.
+
+	See Also:
+		<gfx_unload_texture>
+*/
+int gfx_load_texture_tga(const char *filename);
+
+/*
+	Function: gfx_load_texture_raw
+		Loads a texture from memory.
+	
+	Arguments:
+		w - Width of the texture.
+		h - Height of the texture.
+		data - Pointer to the pixel data.
+	
+	Returns:
+		An ID to the texture. -1 on failure.
+		
+	Remarks:
+		The pixel data should be in RGBA format with 8 bit per component.
+		So the total size of the data should be w*h*4.
+		
+	See Also:
+		<gfx_unload_texture>
+*/
+int gfx_load_texture_raw(int w, int h, const void *data);
+
+/*
+	Function: gfx_texture_set
+		Sets the active texture.
+	
+	Arguments:
+		id - ID to the texture to set.
+*/
+void gfx_texture_set(int id);
+
+/*
+	Function: gfx_unload_texture
+		Unloads a texture.
+	
+	Arguments:
+		id - ID to the texture to unload.
+		
+	See Also:
+		<gfx_load_texture_tga>, <gfx_load_texture_raw>
+		
+	Remarks:
+		NOT IMPLEMENTED
+*/
+int gfx_unload_texture(int id); // NOT IMPLEMENTED
+
+void gfx_clear(float r, float g, float b);
+
+/*
+	Function: gfx_screenwidth
+		Returns the screen width.
+	
+	See Also:
+		<gfx_screenheight>
+*/
+int gfx_screenwidth();
+
+/*
+	Function: gfx_screenheight
+		Returns the screen height.
+	
+	See Also:
+		<gfx_screenwidth>
+*/
+int gfx_screenheight();
+
+/*
+	Function: gfx_mapscreen
+		Specifies the coordinate system for the screen.
+		
+	Arguments:
+		tl_x - Top-left X
+		tl_y - Top-left Y
+		br_x - Bottom-right X
+		br_y - Bottom-right y
+*/
+void gfx_mapscreen(float tl_x, float tl_y, float br_x, float br_y);
+
+/*
+	Function: gfx_blend_normal
+		Set the active blending mode to normal (src, 1-src).
+
+	Remarks:
+		This must be used before calling <gfx_quads_begin>.
+		This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
+	
+	See Also:
+		<gfx_blend_additive>
+*/
+void gfx_blend_normal();
+
+/*
+	Function: gfx_blend_additive
+		Set the active blending mode to additive (src, one).
+
+	Remarks:
+		This must be used before calling <gfx_quads_begin>.
+		This is equal to glBlendFunc(GL_SRC_ALPHA, GL_ONE).
+	
+	See Also:
+		<gfx_blend_normal>
+*/
+void gfx_blend_additive();
+
+/*
+	Function: gfx_quads_begin
+		Begins a quad drawing session.
+		
+	Remarks:
+		This functions resets the rotation, color and subset.
+		End the session by using <gfx_quads_end>.
+		You can't change texture or blending mode during a session.
+		
+	See Also:
+		<gfx_quads_end>
+*/
+void gfx_quads_begin();
+
+/*
+	Function: gfx_quads_end
+		Ends a quad session.
+		
+	See Also:
+		<gfx_quads_begin>
+*/
+void gfx_quads_end();
+
+/*
+	Function: gfx_quads_setrotation
+		Sets the rotation to use when drawing a quad.
+		
+	Arguments:
+		angle - Angle in radians.
+		
+	Remarks:
+		The angle is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setrotation(float angle);
+
+/*
+	Function: gfx_quads_setcolorvertex
+		Sets the color of a vertex.
+		
+	Arguments:
+		i - Index to the vertex.
+		r - Red value.
+		g - Green value.
+		b - Blue value.
+		a - Alpha value.
+		
+	Remarks:
+		The color values are from 0.0 to 1.0.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setcolorvertex(int i, float r, float g, float b, float a);
+
+/*
+	Function: gfx_quads_setcolor
+		Sets the color of all the vertices.
+		
+	Arguments:
+		r - Red value.
+		g - Green value.
+		b - Blue value.
+		a - Alpha value.
+		
+	Remarks:
+		The color values are from 0.0 to 1.0.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setcolor(float r, float g, float b, float a);
+
+/*
+	Function: gfx_quads_setsubset
+		Sets the uv coordinates to use.
+		
+	Arguments:
+		tl_u - Top-left U value.
+		tl_v - Top-left V value.
+		br_u - Bottom-right U value.
+		br_v - Bottom-right V value.
+		
+	Remarks:
+		O,0 is top-left of the texture and 1,1 is bottom-right.
+		The color is reset when <gfx_quads_begin> is called.
+*/
+void gfx_quads_setsubset(float tl_u, float tl_v, float br_u, float br_v);
+
+/*
+	Function: gfx_quads_drawTL
+		Draws a quad by specifying the top-left point.
+		
+	Arguments:
+		x - X coordinate of the top-left corner.
+		y - Y coordinate of the top-left corner.
+		width - Width of the quad.
+		height - Height of the quad.
+		
+	Remarks:
+		Rotation still occurs from the center of the quad.
+		You must call <gfx_quads_begin> before calling this function.
+
+	See Also:
+		<gfx_quads_draw>
+*/
+void gfx_quads_drawTL(float x, float y, float width, float height);
+
+/*
+	Function: gfx_quads_draw
+		Draws a quad by specifying the center point.
+		
+	Arguments:
+		x - X coordinate of the center.
+		y - Y coordinate of the center.
+		width - Width of the quad.
+		height - Height of the quad.
+
+	Remarks:
+		You must call <gfx_quads_begin> before calling this function.
+
+	See Also:
+		<gfx_quads_drawTL>
+*/
+void gfx_quads_draw(float x, float y, float w, float h);
+
+void gfx_quads_draw_freeform(
+	float x0, float y0,
+	float x1, float y1,
+	float x2, float y2,
+	float x3, float y3);
+
+void gfx_quads_text(float x, float y, float size, const char *text);
+
+// sound (client)
+enum
+{
+	SND_PLAY_ONCE = 0,
+	SND_LOOP
+};
+	
+bool snd_init();
+int snd_load_wav(const char *filename);
+int snd_play(int sound, int loop = SND_PLAY_ONCE, float vol = 1.0f, float pan = 0.0f);
+void snd_stop(int id);
+void snd_set_vol(int id, float vol);
+bool snd_shutdown();
+
+/*
+	Group: Input
+*/
+
+/*
+	Function: inp_mouse_relative
+		Fetches the mouse movements.
+		
+	Arguments:
+		x - Pointer to the variable that should get the X movement.
+		y - Pointer to the variable that should get the Y movement.
+*/
+void inp_mouse_relative(int *x, int *y);
+
+/*
+	Function: inp_mouse_button_pressed
+		Checks if a mouse button is pressed.
+		
+	Arguments:
+		button - Index to the button to check.
+			* 0 - Left mouse button.
+			* 1 - Right mouse button.
+			* 2 - Middle mouse button.
+			* Others over 2 is undefined mouse buttons.
+			
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+*/
+int inp_mouse_button_pressed(int button);
+
+/*
+	Function: inp_key_pressed
+		Checks if a key is pressed.
+		
+	Arguments:
+		key - Index to the key to check
+		
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+	
+	Remarks:
+		Check baselib/include/baselib/keys.h for the keys.
+*/
+int inp_key_pressed(int key);
+
+/*
+	Group: Map
+*/
+
+int map_load(const char *mapname); // NOT EXPOSED
+void map_unload(); // NOT EXPOSED
+
+/*
+	Function: map_is_loaded
+		Checks if a map is loaded.
+		
+	Returns:
+		Returns 1 if the button is pressed, otherwise 0.
+*/
+int map_is_loaded();
+
+/*
+	Function: map_num_items
+		Checks the number of items in the loaded map.
+		
+	Returns:
+		Returns the number of items. 0 if no map is loaded.
+*/
+int map_num_items();
+
+/*
+	Function: map_find_item
+		Searches the map for an item.
+	
+	Arguments:
+		type - Item type.
+		id - Item ID.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise it returns NULL.
+*/
+void *map_find_item(int type, int id);
+
+/*
+	Function: map_get_item
+		Gets an item from the loaded map from index.
+	
+	Arguments:
+		index - Item index.
+		type - Pointer that recives the item type (can be NULL).
+		id - Pointer that recives the item id (can be NULL).
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise it returns NULL.
+*/
+void *map_get_item(int index, int *type, int *id);
+
+/*
+	Function: map_get_type
+		Gets the index range of an item type.
+	
+	Arguments:
+		type - Item type to search for.
+		start - Pointer that recives the starting index.
+		num - Pointer that recives the number of items.
+	
+	Returns:
+		If the item type is not in the map, start and num will be set to 0.
+*/
+void map_get_type(int type, int *start, int *num);
+
+/*
+	Function: map_get_data
+		Fetches a pointer to a raw data chunk in the map.
+	
+	Arguments:
+		index - Index to the data to fetch.
+	
+	Returns:
+		A pointer to the raw data, otherwise 0.
+*/
+void *map_get_data(int index);
+
+/*
+	Group: Network (Server)
+*/
+/*
+	Function: snap_new_item
+		Creates a new item that should be sent.
+	
+	Arguments:
+		type - Type of the item.
+		id - ID of the item.
+		size - Size of the item.
+	
+	Returns:
+		A pointer to the item data, otherwise 0.
+	
+	Remarks:
+		The item data should only consist pf 4 byte integers as
+		they are subject to byte swapping. This means that the size
+		argument should be dividable by 4.
+*/
+void *snap_new_item(int type, int id, int size);
+
+/*
+	Group: Network (Client)
+*/
+/*
+	Function: snap_num_items
+		Check the number of items in a snapshot.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+	
+	Returns:
+		The number of items in the snapshot.
+*/
+int snap_num_items(int snapid);
+
+/*
+	Function: snap_get_item
+		Gets an item from a snapshot.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+		index - Index of the item.
+		item - Pointer that recives the item info.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise NULL.
+*/
+void *snap_get_item(int snapid, int index, snap_item *item);
+
+/*
+	Function: snap_find_item
+		Searches a snapshot for an item.
+	
+	Arguments:
+		snapid - Snapshot ID to the data to fetch.
+			* SNAP_PREV for previous snapshot.
+			* SNAP_CUR for current snapshot.
+		type - Type of the item.
+		id - ID of the item.
+	
+	Returns:
+		Returns a pointer to the item if it exists, otherwise NULL.
+*/
+void *snap_find_item(int snapid, int type, int id);
+
+/*
+	Function: snap_input
+		Sets the input data to send to the server.
+	
+	Arguments:
+		data - Pointer to the data.
+		size - Size of the data.
+
+	Remarks:
+		The data should only consist of 4 bytes integer as they are
+		subject to byte swapping.
+*/
+void snap_input(void *data, int size);
+
+/*
+	Function: snap_intratick
+		Returns the intra-tick mixing value.
+
+	Returns:
+		Returns the mixing value between the previous snapshot
+		and the current snapshot. 
+
+	Remarks:
+		DOCTODO: Explain how to use it.
+*/
+float snap_intratick();
+
+/*
+	Group: Server Callbacks
+*/
+/*
+	Function: mods_init
+		Called when the server is started.
+	
+	Remarks:
+		It's called after the map is loaded so all map items are available.
+*/
+void mods_init();
+
+/*
+	Function: mods_shutdown
+		Called when the server quits.
+	
+	Remarks:
+		Should be used to clean up all resources used.
+*/
+void mods_shutdown();
+
+/*
+	Function: mods_client_enter
+		Called when a client has joined the game.
+		
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+	
+	Remarks:
+		It's called when the client is finished loading and should enter gameplay.
+*/
+void mods_client_enter(int cid);
+
+/*
+	Function: mods_client_drop
+		Called when a client drops from the server.
+
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS
+*/
+void mods_client_drop(int cid);
+
+/*
+	Function: mods_client_input
+		Called when the server recives new input from a client.
+
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+		input - Pointer to the input data.
+		size - Size of the data. (NOT IMPLEMENTED YET)
+*/
+void mods_client_input(int cid, void *input);
+
+/*
+	Function: mods_tick
+		Called with a regular interval to progress the gameplay.
+		
+	Remarks:
+		The SERVER_TICK_SPEED tells the number of ticks per second.
+*/
+void mods_tick();
+
+/*
+	Function: mods_presnap
+		Called before the server starts to construct snapshots for the clients.
+*/
+void mods_presnap();
+
+/*
+	Function: mods_snap
+		Called to create the snapshot for a client.
+	
+	Arguments:
+		cid - Client ID. Is 0 - MAX_CLIENTS.
+	
+	Remarks:
+		The game should make a series of calls to <snap_new_item> to construct
+		the snapshot for the client.
+*/
+void mods_snap(int cid);
+
+/*
+	Function: mods_postsnap
+		Called after the server is done sending the snapshots.
+*/
+void mods_postsnap();
+
+/*
+	Group: Client Callbacks
+*/
+/*
+	Function: modc_init
+		Called when the client starts.
+	
+	Remarks:
+		The game should load resources that are used during the entire
+		time of the game. No map is loaded.
+*/
+void modc_init();
+
+/*
+	Function: modc_newsnapshot
+		Called when the client progressed to a new snapshot.
+	
+	Remarks:
+		The client can check for items in the snapshot and perform one time
+		events like playing sounds, spawning client side effects etc.
+*/
+void modc_newsnapshot();
+
+/*
+	Function: modc_entergame
+		Called when the client has successfully connect to a server and
+		loaded a map.
+	
+	Remarks:
+		The client can check for items in the map and load them.
+*/
+void modc_entergame();
+
+/*
+	Function: modc_shutdown
+		Called when the client closes down.
+*/
+void modc_shutdown();
+
+/*
+	Function: modc_render
+		Called every frame to let the game render it self.
+*/
+void modc_render();
+
+
+
+/*
+    Group: Menu Callbacks
+*/
+/*
+    Function: modmenu_init
+        Called when the menu starts.
+    
+    Remarks:
+        The menu should load resources that are used during the entire
+        time of the menu use.
+*/
+void modmenu_init();
+
+/*
+    Function: modmenu_shutdown
+        Called when the menu closes down.
+*/
+void modmenu_shutdown();
+
+/*
+    Function: modmenu_render
+        Called every frame to let the menu render it self.
+*/
+int modmenu_render(void *server_address, char *name, int max_len);
+
+void snap_encode_string(const char *src, int *dst, int length, int max_length);
+void snap_decode_string(const int *src, char *dst, int length);
+
+int server_getclientinfo(int client_id, client_info *info);
+int server_tick();
+int server_tickspeed();
+
+int inp_key_was_pressed(int key);
+int inp_key_down(int key);
+void inp_update();
+float client_frametime();
+float client_localtime();
+
+#define MASTER_SERVER_ADDRESS "teewars.com"
+#define MASTER_SERVER_PORT 8300
+
+
+
+#endif
diff --git a/src/lzw.cpp b/src/lzw.cpp
new file mode 100644
index 00000000..52ca569d
--- /dev/null
+++ b/src/lzw.cpp
@@ -0,0 +1,213 @@
+#include <string.h>
+
+// LZW Compressor
+struct SYM
+{
+	unsigned char data[1024*2];
+	int size;
+	int next;
+};
+
+struct SYMBOLS
+{
+	SYM syms[512];
+	int jumptable[256];
+	int currentsym;
+};
+
+static SYMBOLS symbols;
+
+// symbol info
+inline int sym_size(int i) { return symbols.syms[i].size; }
+inline unsigned char *sym_data(int i) { return symbols.syms[i].data; }
+
+static void sym_init()
+{
+	for(int i = 0; i < 256; i++)
+	{
+		symbols.syms[i].data[0] = (unsigned char)i;
+		symbols.syms[i].size = 1;
+		symbols.jumptable[i] = -1;
+	}
+
+	for(int i = 0; i < 512; i++)
+		symbols.syms[i].next = -1;
+
+	symbols.currentsym = 0;
+}
+
+static void sym_index(int sym)
+{
+	int table = symbols.syms[sym].data[0];
+	symbols.syms[sym].next = symbols.jumptable[table];
+	symbols.jumptable[table] = sym;
+}
+
+static void sym_unindex(int sym)
+{
+	int table = symbols.syms[sym].data[0];
+	int prev = -1;
+	int current = symbols.jumptable[table];
+
+	while(current != -1)
+	{
+		if(current == sym)
+		{
+			if(prev != -1)
+				symbols.syms[prev].next = symbols.syms[current].next;
+			else
+				symbols.jumptable[table] = symbols.syms[current].next;
+			break;
+		}
+
+		prev = current;
+		current = symbols.syms[current].next;
+	}
+}
+
+static int sym_add(unsigned char *sym, long len)
+{
+	int i = 256+symbols.currentsym;
+
+	memcpy(symbols.syms[i].data, sym, len);
+	symbols.syms[i].size = len;
+	symbols.currentsym = (i+1)%255;
+	return i;
+}
+
+static int sym_add_and_index(unsigned char *sym, long len)
+{
+	if(symbols.syms[256+symbols.currentsym].size)
+		sym_unindex(256+symbols.currentsym);
+	int s = sym_add(sym, len);
+	sym_index( s);
+	return s;
+}
+
+static void sym_append(int sym, unsigned char extra)
+{
+	symbols.syms[sym].data[symbols.syms[sym].size] = extra;
+	symbols.syms[sym].size++;
+}
+
+static int sym_find(unsigned char *data, int size)
+{
+	int best = data[0];
+	int bestlen = 1;
+	int current = symbols.jumptable[data[0]];
+
+	while(current != -1)
+	{
+		if(symbols.syms[current].size <= size && memcmp(data, symbols.syms[current].data, symbols.syms[current].size) == 0)
+		{
+			if(bestlen < symbols.syms[current].size)
+			{
+				bestlen = symbols.syms[current].size;
+				best = current;
+			}
+		}
+
+		current = symbols.syms[current].next;
+	}		
+
+	return best;
+}
+
+//
+// compress
+//
+long lzw_compress(const void *src_, int size, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *end = (unsigned char *)src_+size;
+	unsigned char *dst = (unsigned char *)dst_;
+	long left = (end-src);
+
+	// init symboltable
+	sym_init();
+
+	bool done = false;
+	while(!done)
+	{
+		unsigned char *flagptr = dst;
+		unsigned char flagbits = 0;
+		int b = 0;
+
+		dst++; // skip a byte where the flags are
+
+		for(; b < 8; b++)
+		{
+			if(left <= 0) // check for EOF
+			{
+				// write EOF symbol
+				flagbits |= 1<<b;
+				*dst++ = 255;
+				done = true;
+				break;
+			}
+
+ 			int sym = sym_find( src, left);
+			int symsize = sym_size( sym);
+
+			if(sym&0x100)
+				flagbits |= 1<<b; // add bit that says that its a symbol
+
+			*dst++ = sym&0xff; // set symbol
+
+			if(left > symsize+1) // create new symbol
+				sym_add_and_index( src, symsize+1);
+			
+			src += symsize; // advance src
+			left -= symsize;
+		}
+
+		// write the flags
+		*flagptr = flagbits;
+	}
+
+	return (long)(dst-(unsigned char*)dst_);
+}
+
+//
+// decompress
+//
+long lzw_decompress(const void *src_, void *dst_)
+{
+	unsigned char *src = (unsigned char *)src_;
+	unsigned char *dst = (unsigned char *)dst_;
+	int previtem = -1; // 0-255 = raw byte, 256+ = symbol
+	int item;
+
+	sym_init();
+
+	while(1)
+	{
+		unsigned char flagbits = 0;
+		flagbits = *src++; // read flags
+
+		int b = 0;
+		for(; b < 8; b++)
+		{
+			item = *src++;
+			if(flagbits&(1<<b))
+				item |= 256;
+
+			if(item == 0x1ff) // EOF symbol
+				return (dst-(unsigned char *)dst_);
+
+			if(previtem != -1) // this one could be removed
+			{
+				// the previous item can 
+				int s = sym_add( sym_data( previtem), sym_size( previtem));
+				sym_append(s, sym_data( item)[0]);
+			}
+				
+			memcpy(dst, sym_data( item), sym_size( item));
+			dst += sym_size( item);
+			previtem = item;
+		}
+
+	}
+
+	return 0;
+}
diff --git a/src/lzw.h b/src/lzw.h
new file mode 100644
index 00000000..af29665e
--- /dev/null
+++ b/src/lzw.h
@@ -0,0 +1,2 @@
+long lzw_compress(const void *src, int size, void *dst);
+long lzw_decompress(const void *src, void *dst);
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 00000000..d625fbff
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,25 @@
+#include <baselib/system.h>
+#include <baselib/network.h>
+
+#include <stdio.h>
+
+using namespace baselib;
+
+extern int client_main(int argc, char **argv);
+extern int editor_main(int argc, char **argv);
+extern int server_main(int argc, char **argv);
+
+int main(int argc, char **argv)
+{
+	// search for server or editor argument
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 's' && argv[i][2] == 0)
+			return server_main(argc, argv);
+		else if(argv[i][0] == '-' && argv[i][1] == 'e' && argv[i][2] == 0)
+			return editor_main(argc, argv);
+	}
+
+	// no specific parameters, start the client
+	return client_main(argc, argv);
+}
diff --git a/src/map.cpp b/src/map.cpp
new file mode 100644
index 00000000..3e76547e
--- /dev/null
+++ b/src/map.cpp
@@ -0,0 +1,48 @@
+#include <baselib/system.h>
+#include <baselib/stream/file.h>
+
+#include "datafile.h"
+
+static datafile *map;
+
+void *map_get_data(int index)
+{
+	return datafile_get_data(map, index);
+}
+
+void *map_get_item(int index, int *type, int *id)
+{
+	return datafile_get_item(map, index, type, id);
+}
+
+void map_get_type(int type, int *start, int *num)
+{
+	datafile_get_type(map, type, start, num);
+}
+
+void *map_find_item(int type, int id)
+{
+	return datafile_find_item(map, type, id);
+}
+
+int map_num_items()
+{
+	return datafile_num_items(map);
+}
+
+void map_unload()
+{
+	datafile_unload(map);
+	map = 0x0;
+}
+
+int map_is_loaded()
+{
+	return map != 0;
+}
+
+int map_load(const char *mapname)
+{
+	map = datafile_load(mapname);
+	return map != 0;
+}
diff --git a/src/menu.cpp b/src/menu.cpp
new file mode 100644
index 00000000..b5e6c0eb
--- /dev/null
+++ b/src/menu.cpp
@@ -0,0 +1,597 @@
+#include <stdio.h>
+#include <math.h>
+#include <baselib/system.h>
+#include <baselib/keys.h>
+#include <baselib/mouse.h>
+#include <baselib/network.h>
+
+#include "interface.h"
+#include "ui.h"
+
+#include "game/mapres_image.h"
+#include "game/mapres_tilemap.h"
+#include "game/mapres.h"
+
+using namespace baselib;
+
+/********************************************************
+ MENU                                                  
+*********************************************************/
+
+struct pretty_font
+{
+    char m_CharStartTable[256];
+    char m_CharEndTable[256];
+    int font_texture;
+};  
+
+extern pretty_font *current_font;
+void gfx_pretty_text(float x, float y, float size, const char *text);
+float gfx_pretty_text_width(float size, const char *text);
+
+void draw_scrolling_background(int id, float w, float h, float t)
+{
+	float tx = w/256.0f;
+	float ty = h/256.0f;
+
+	float start_x = fmod(t, 1.0f);
+	float start_y = 1.0f - fmod(t*0.8f, 1.0f);
+
+    gfx_blend_normal();
+    gfx_texture_set(id);
+    gfx_quads_begin();
+    gfx_quads_setcolor(1,1,1,1);
+	gfx_quads_setsubset(
+		start_x, // startx
+		start_y, // starty
+		start_x+tx, // endx
+		start_y+ty); // endy								
+    gfx_quads_drawTL(0.0f,0.0f,w,h);
+    gfx_quads_end();
+}
+
+int background_texture;
+int not_empty_item_texture;
+int empty_item_texture;
+int active_item_texture;
+int selected_item_texture;
+int join_button_texture;
+int join_button_hot_texture;
+int join_button_active_texture;
+int join_button_grey_texture;
+int quit_button_texture;
+int quit_button_hot_texture;
+int quit_button_active_texture;
+int up_button_texture;
+int up_button_active_texture;
+int down_button_texture;
+int down_button_active_texture;
+int teewars_banner_texture;
+int scroll_indicator_texture;
+int connect_localhost_texture;
+int refresh_button_texture;
+int refresh_button_hot_texture;
+int refresh_button_active_texture;
+int input_box_texture;
+
+int music_menu;
+int music_menu_id = -1;
+
+struct button_textures
+{
+	int *normal_texture;
+	int *hot_texture;
+	int *active_texture;
+};
+
+button_textures connect_localhost_button = { &connect_localhost_texture, &connect_localhost_texture, &connect_localhost_texture };
+button_textures join_button = { &join_button_texture, &join_button_hot_texture, &join_button_active_texture };
+button_textures quit_button = { &quit_button_texture, &quit_button_hot_texture, &quit_button_active_texture };
+button_textures scroll_up_button = { &up_button_texture, &up_button_texture, &up_button_active_texture };
+button_textures scroll_down_button = { &down_button_texture, &down_button_texture, &down_button_active_texture };
+button_textures list_item_button = { &not_empty_item_texture, &active_item_texture, &active_item_texture };
+button_textures selected_item_button = { &selected_item_texture, &selected_item_texture, &selected_item_texture };
+button_textures refresh_button = { &refresh_button_texture, &refresh_button_hot_texture, &refresh_button_active_texture };
+
+void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra)
+{
+	button_textures *tx = (button_textures *)extra;
+
+    gfx_blend_normal();
+	
+	if (ui_active_item() == id && ui_hot_item() == id)
+		gfx_texture_set(*tx->active_texture);
+	else if (ui_hot_item() == id)
+		gfx_texture_set(*tx->hot_texture);
+	else
+		gfx_texture_set(*tx->normal_texture);
+	
+    gfx_quads_begin();
+
+    gfx_quads_setcolor(1,1,1,1);
+
+    gfx_quads_drawTL(x,y,w,h);
+    gfx_quads_end();
+
+    gfx_texture_set(current_font->font_texture);
+    gfx_pretty_text(x + 4, y - 3.5f, 18.f, text);
+}
+
+void draw_image_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra)
+{
+	ui_do_image(*(int *)id, x, y, w, h);
+}
+
+struct server_info
+{
+	int version;
+    int players;
+	int max_players;
+	netaddr4 address;
+	char name[129];
+	char map[65];
+};
+
+struct server_list
+{   
+    server_info infos[10];
+	int active_count, info_count;
+	int scroll_index;
+	int selected_index;
+};
+#include <string.h>
+
+int ui_do_edit_box(void *id, float x, float y, float w, float h, char *str, int str_size)
+{
+    int inside = ui_mouse_inside(x, y, w, h);
+	int r = 0;
+
+	if(inside)
+	{
+		ui_set_hot_item(id);
+
+		if(ui_mouse_button(0))
+			ui_set_active_item(id);
+	}
+
+	if (ui_active_item() == id)
+	{
+		char c = keys::last_char();
+		int k = keys::last_key();
+		int len = strlen(str);
+	
+		if (c >= 32 && c < 128)
+		{
+			if (len < str_size - 1)
+			{
+				str[len] = c;
+				str[len+1] = 0;
+			}
+		}
+
+		if (k == keys::backspace)
+		{
+			if (len > 0)
+				str[len-1] = 0;
+		}
+		r = 1;
+	}
+
+	ui_do_label(x + 4, y - 3.5f, str);
+
+	if (ui_active_item() == id)
+	{
+		float w = gfx_pretty_text_width(18.0f, str);
+		ui_do_label(x + 4 + w, y - 3.5f, "_");
+	}
+
+	return r;
+}
+
+int do_scroll_bar(void *id, float x, float y, float height, int steps, int last_index)
+{
+	int r = last_index;
+
+    if (ui_do_button(&up_button_texture, "", 0, x, y, 8, 8, draw_menu_button, &scroll_up_button))
+	{
+		if (r > 0)
+			--r;
+	}
+    else if (ui_do_button(&down_button_texture, "", 0, x, y + height - 8, 8, 8, draw_menu_button, &scroll_down_button))
+	{
+		if (r < steps)
+			++r;
+	}
+	else if (steps > 0) // only if there's actually stuff to scroll through
+	{
+		int inside = ui_mouse_inside(x, y + 8, 8, height - 16);
+        if (inside && (!ui_active_item() || ui_active_item() == id))
+			ui_set_hot_item(id);
+
+		if(ui_active_item() == id)
+		{
+			if (ui_mouse_button(0))
+			{
+				float pos = ui_mouse_y() - y - 8;
+				float perc = pos / (height - 16);
+
+				r = (steps + 1) * perc;
+				if (r < 0)
+					r = 0;
+				else if (r > steps)
+					r = steps;
+			}
+			else
+				ui_set_active_item(0);
+		}
+		else if (ui_hot_item() == id && ui_mouse_button(0))
+			ui_set_active_item(id);
+		else if (inside && (!ui_active_item() || ui_active_item() == id))
+			ui_set_hot_item(id);
+	}
+
+	ui_do_image(scroll_indicator_texture, x, y + 8 + r * ((height - 32) / steps), 8, 16);
+	
+	return r;
+}
+
+int do_server_list(server_list *list, float x, float y, int visible_items)
+{
+	const float spacing = 1.5f;
+	const float item_height = 14;
+	const float item_width = 364;
+	const float real_width = item_width + 10;
+	const float real_height = item_height * visible_items + spacing * (visible_items - 1);
+
+	int r = -1;
+
+	for (int i = 0; i < visible_items; i++)
+	{
+		int item_index = i + list->scroll_index;
+		if (item_index >= list->active_count)
+			ui_do_image(empty_item_texture, x, y + i * item_height + i * spacing, item_width, item_height);
+		else
+		{
+			server_info *item = &list->infos[item_index];
+
+			bool clicked = false;
+			if (list->selected_index == item_index)
+				clicked = ui_do_button(item, item->name, 0, x, y + i * item_height + i * spacing, item_width, item_height, draw_menu_button, &selected_item_button);
+			else
+				clicked = ui_do_button(item, item->name, 0, x, y + i * item_height + i * spacing, item_width, item_height, draw_menu_button, &list_item_button);
+
+			char temp[64]; // plenty of extra room so we don't get sad :o
+			sprintf(temp, "%i/%i", item->players, item->max_players);
+
+			gfx_texture_set(current_font->font_texture);
+			gfx_pretty_text(x + 300, y + i * item_height + i * spacing - 3.5f, 18.f, temp);
+            gfx_pretty_text(x + 180, y + i * item_height + i * spacing - 3.5f, 18.f, item->map);
+
+			if (clicked)
+			{
+				r = item_index;
+				list->selected_index = item_index;
+			}
+		}
+	}
+
+	list->scroll_index = do_scroll_bar(&list->scroll_index, x + real_width - 8, y, real_height, list->active_count - visible_items, list->scroll_index);
+	
+	return r;
+}
+
+#include <cstring>
+
+char *read_int(char *buffer, int *value)
+{
+    *value = buffer[0] << 24;
+    *value |= buffer[1] << 16;
+    *value |= buffer[2] << 8;
+    *value |= buffer[3];
+
+	return buffer + 4;
+}
+
+char *read_netaddr(char *buffer, netaddr4 *addr)
+{
+	addr->ip[0] = *buffer++;
+	addr->ip[1] = *buffer++;
+	addr->ip[2] = *buffer++;
+	addr->ip[3] = *buffer++;
+
+	int port;
+	buffer = read_int(buffer, &port);
+
+	addr->port = port;
+
+	return buffer;
+}
+
+void refresh_list(server_list *list)
+{
+	netaddr4 addr;
+	netaddr4 me(0, 0, 0, 0, 0);
+
+	list->selected_index = -1;
+	
+	if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &addr) == 0)
+    {
+        socket_tcp4 sock;
+        sock.open(&me);
+
+		//sock.set_non_blocking();
+
+		// try and connect with a timeout of 1 second
+        if (sock.connect_non_blocking(&addr))
+        {
+            char data[256];
+            int total_received = 0;
+            int pointer = 0;
+            int received;
+
+            int master_server_version = -1;
+            int server_count = -1;
+
+            // read header
+            while (total_received < 12 && (received = sock.recv(data + total_received, 12 - total_received)) > 0)
+                total_received += received;
+
+            // see if we have the header
+            if (total_received == 12)
+            {
+                int signature;
+                read_int(data, &signature);
+    
+                // check signature
+                if (signature == 'TWSL')
+                {
+                    read_int(data + 4, &master_server_version);
+                    read_int(data + 8, &server_count);
+
+                    // TODO: handle master server version O.o
+                        
+                    const int server_info_size = 212;
+                    const int wanted_data_count = server_count * server_info_size;
+
+                    list->active_count = 0;
+    
+                    for (int i = 0; i < server_count; i++)
+                    { 
+                         total_received = 0;
+
+                        // read data for a server
+                        while (sock.is_connected() && total_received < server_info_size && (received = sock.recv(data + total_received, server_info_size - total_received)) > 0)
+                            total_received += received;
+
+                        // check if we got enough data
+                        if (total_received == server_info_size)
+                        {
+                            char *d = data;
+
+                            server_info *info = &list->infos[i];
+
+                            d = read_int(d, &info->version);
+                            d = read_netaddr(d, &info->address);
+
+							//dbg_msg("menu/got_serverinfo", "IP: %i.%i.%i.%i:%i", (int)info->address.ip[0], (int)info->address.ip[1], (int)info->address.ip[2], (int)info->address.ip[3], info->address.port);
+
+                            d = read_int(d, &info->players);
+                            d = read_int(d, &info->max_players);
+                            memcpy(info->name, d, 128);
+                            d += 128;
+							memcpy(info->map, d, 64);
+
+                            // let's be safe.
+                            info->name[128] = 0;
+                            info->map[64] = 0;
+
+                            ++list->active_count;
+                        }
+                        else
+                            break;
+                    }
+
+                    if (list->scroll_index >= list->active_count)
+                        list->scroll_index = list->active_count - 1;
+
+                    if (list->scroll_index < 0)
+                        list->scroll_index = 0;
+                }
+            }
+
+            sock.close();
+        }
+	}
+}
+
+static int menu_render(netaddr4 *server_address, char *str, int max_len)
+{
+	// background color
+	gfx_clear(89/255.f,122/255.f,0.0);
+
+	// GRADIENT: top to bottom
+	// top color: 60, 80, 0
+	// bottom color: 90, 120, 0
+	
+	// world coordsys
+	float zoom = 3.0f;
+	gfx_mapscreen(0,0,400.0f*zoom,300.0f*zoom);
+
+	// GUI coordsys
+	gfx_mapscreen(0,0,400.0f,300.0f);
+
+	static server_list list;
+	static bool inited = false;
+
+	if (!inited)
+	{
+		list.info_count = 256;
+
+		list.scroll_index = 0;
+		list.selected_index = -1;
+
+		inited = true;
+
+		refresh_list(&list);
+	}
+
+	static int64 start = time_get();
+
+	float t = double(time_get() - start) / double(time_freq());
+	draw_scrolling_background(background_texture, 400, 300, t * 0.01);
+
+	ui_do_image(teewars_banner_texture, 70, 10, 256, 64);
+
+	do_server_list(&list, 10, 80, 8);
+
+	/*
+    if (ui_do_button(&connect_localhost_button, "", 0, 15, 250, 64, 24, draw_menu_button, &connect_localhost_button))
+    {
+        *server_address = netaddr4(127, 0, 0, 1, 8303);
+        return 1;
+    }*/	
+
+	if (ui_do_button(&refresh_button, "", 0, 220, 210, 64, 24, draw_menu_button, &refresh_button))
+	{
+		refresh_list(&list);
+	} 
+
+	if (list.selected_index == -1)
+	{
+		ui_do_image(join_button_grey_texture, 290, 210, 64, 24);
+	}
+	else if (ui_do_button(&join_button, "", 0, 290, 210, 64, 24, draw_menu_button, &join_button))
+	{
+		*server_address = list.infos[list.selected_index].address;
+
+		dbg_msg("menu/join_button", "IP: %i.%i.%i.%i:%i", (int)server_address->ip[0], (int)server_address->ip[1], (int)server_address->ip[2], (int)server_address->ip[3], server_address->port);
+
+		return 1;
+	}
+
+	const float name_x = 10, name_y = 215;
+
+	ui_do_label(name_x + 4, name_y - 3.5f, "Name:");
+	ui_do_image(input_box_texture, name_x + 50 - 5, name_y - 5, 150 + 10, 14 + 10);
+	ui_do_edit_box(str, name_x + 50, name_y, 150, 14, str, max_len);
+
+	if (ui_do_button(&quit_button, "", 0, 290, 250, 69, 25, draw_menu_button, &quit_button))
+		return -1;
+
+	return 0;
+}
+
+void modmenu_init()
+{
+	keys::enable_char_cache();
+	keys::enable_key_cache();
+
+    current_font->font_texture = gfx_load_texture_tga("data/big_font.tga");
+	background_texture = gfx_load_texture_tga("data/gui_bg.tga");
+    not_empty_item_texture = gfx_load_texture_tga("data/gui/game_list_item_not_empty.tga");
+    empty_item_texture = gfx_load_texture_tga("data/gui/game_list_item_empty.tga");
+    active_item_texture = gfx_load_texture_tga("data/gui/game_list_item_active.tga");
+	selected_item_texture = gfx_load_texture_tga("data/gui/game_list_item_selected.tga");
+
+	join_button_texture = gfx_load_texture_tga("data/gui/join_button.tga");
+	join_button_hot_texture = gfx_load_texture_tga("data/gui/join_button_hot.tga");
+	join_button_active_texture = gfx_load_texture_tga("data/gui/join_button_active.tga");
+	join_button_grey_texture = gfx_load_texture_tga("data/gui/join_button_greyed.tga");
+
+
+//    button_not_hilighted_texture = gfx_load_texture_tga("data/gui/game_list_join_button.tga");
+//	button_hilighted_texture = gfx_load_texture_tga("data/gui/button_hilighted.tga");
+//	button_active_texture = gfx_load_texture_tga("data/gui/button_active.tga");
+
+    quit_button_texture = gfx_load_texture_tga("data/gui/quit_button.tga");
+	quit_button_hot_texture = gfx_load_texture_tga("data/gui/quit_button_hot.tga");
+	quit_button_active_texture = gfx_load_texture_tga("data/gui/quit_button_active.tga");
+
+    up_button_texture = gfx_load_texture_tga("data/gui/scroll_arrow_up.tga");
+	up_button_active_texture = gfx_load_texture_tga("data/gui/scroll_arrow_up_active.tga");
+
+    down_button_texture = gfx_load_texture_tga("data/gui/scroll_arrow_down.tga");
+	down_button_active_texture = gfx_load_texture_tga("data/gui/scroll_arrow_down_active.tga");
+
+    teewars_banner_texture = gfx_load_texture_tga("data/gui_logo.tga");
+    scroll_indicator_texture = gfx_load_texture_tga("data/gui/scroll_drag.tga");
+	connect_localhost_texture = gfx_load_texture_tga("data/gui/game_list_connect_localhost.tga");
+
+	refresh_button_texture = gfx_load_texture_tga("data/gui/refresh_button.tga");
+	refresh_button_hot_texture = gfx_load_texture_tga("data/gui/refresh_button_hot.tga");
+	refresh_button_active_texture = gfx_load_texture_tga("data/gui/refresh_button_active.tga");
+
+	input_box_texture = gfx_load_texture_tga("data/gui/input_box.tga");
+
+	music_menu = snd_load_wav("data/audio/Music_Menu.wav");
+}
+
+void modmenu_shutdown()
+{
+}
+
+int modmenu_render(void *ptr, char *str, int max_len)
+{
+	static int mouse_x = 0;
+	static int mouse_y = 0;
+
+	if (music_menu_id == -1)
+	{
+		dbg_msg("menu", "no music is playing, so let's play some tunes!");
+		music_menu_id = snd_play(music_menu, SND_LOOP);
+	}
+
+	netaddr4 *server_address = (netaddr4 *)ptr;	
+
+    // handle mouse movement
+    float mx, my, mwx, mwy;
+    {
+        int rx, ry;
+        inp_mouse_relative(&rx, &ry);
+        mouse_x += rx;
+        mouse_y += ry;
+        if(mouse_x < 0) mouse_x = 0;
+        if(mouse_y < 0) mouse_y = 0;
+        if(mouse_x > gfx_screenwidth()) mouse_x = gfx_screenwidth();
+        if(mouse_y > gfx_screenheight()) mouse_y = gfx_screenheight();
+            
+        // update the ui
+        mx = (mouse_x/(float)gfx_screenwidth())*400.0f;
+        my = (mouse_y/(float)gfx_screenheight())*300.0f;
+        mwx = mx*3.0f; // adjust to zoom and offset
+        mwy = mx*3.0f; // adjust to zoom and offset
+            
+        int buttons = 0;
+        if(inp_mouse_button_pressed(0)) buttons |= 1;
+        if(inp_mouse_button_pressed(1)) buttons |= 2;
+        if(inp_mouse_button_pressed(2)) buttons |= 4;
+            
+        ui_update(mx,my,mx*3.0f,my*3.0f,buttons);
+    }
+
+    int r = menu_render(server_address, str, max_len);
+
+    // render butt ugly mouse cursor
+    gfx_texture_set(-1);
+    gfx_quads_begin();
+    gfx_quads_setcolor(0,0,0,1);
+    gfx_quads_draw_freeform(mx,my,mx,my,
+                                mx+7,my,
+                                mx,my+7);
+    gfx_quads_setcolor(1,1,1,1);
+    gfx_quads_draw_freeform(mx+1,my+1,mx+1,my+1,
+                                mx+5,my+1,
+                                mx+1,my+5);
+    gfx_quads_end();
+
+	if (r)
+	{
+		snd_stop(music_menu_id);
+		music_menu_id = -1;
+	}
+
+	keys::clear_char();
+	keys::clear_key();
+
+	return r;
+}
diff --git a/src/packet.h b/src/packet.h
new file mode 100644
index 00000000..62784044
--- /dev/null
+++ b/src/packet.h
@@ -0,0 +1,297 @@
+#include <baselib/stream/file.h>
+#include <baselib/network.h>
+
+// TODO: this is not KISS
+class packet
+{
+protected:
+	enum
+	{
+		MAX_PACKET_SIZE = 1024,
+	};
+	
+	// packet data
+	struct header
+	{
+		unsigned msg;
+		unsigned ack;
+		unsigned seq;
+	};
+	
+	unsigned char packet_data[MAX_PACKET_SIZE-sizeof(header)];
+	unsigned char *current;
+	
+	// these are used to prepend data in the packet
+	// used for debugging so we have checks that we 
+	// pack and unpack the same way
+	enum
+	{
+		DEBUG_TYPE_SHIFT=24,
+		DEBUG_SIZE_MASK=0xffff,
+		DEBUG_TYPE_INT=0x1,
+		DEBUG_TYPE_STR=0x2,
+		DEBUG_TYPE_RAW=0x3,
+	};
+	
+	// writes an int to the packet
+	void write_int_raw(int i)
+	{
+		// TODO: byteorder fix, should make sure that it writes big endian
+		// TODO: check for overflow
+		*(int*)current = i;
+		current += sizeof(int);
+	}
+
+	// reads an int from the packet
+	void read_int_raw(int *i)
+	{
+		// TODO: byteorder fix, should make sure that it reads big endian
+		// TODO: check for overflow
+		*i = *(int*)current;
+		current += sizeof(int);
+	}
+	
+public:
+	packet(unsigned msg=0)
+	{
+		current = packet_data;
+		current += sizeof(header);
+		((header*)packet_data)->msg = msg;
+	}
+	
+	void set_header(unsigned ack, unsigned seq)
+	{
+		((header*)packet_data)->ack = ack;
+		((header*)packet_data)->seq = seq;
+	}
+	
+	// writes an int to the packet
+	void write_int(int i)
+	{
+		write_int_raw((DEBUG_TYPE_INT<<DEBUG_TYPE_SHIFT) | 4);
+		write_int_raw(i);
+	}
+
+	void write_raw(const char *raw, int size)
+	{
+		write_int_raw((DEBUG_TYPE_RAW<<DEBUG_TYPE_SHIFT) | size);
+		while(size--)
+			*current++ = *raw++;
+	}
+
+	// writes a string to the packet
+	void write_str(const char *str, int storagesize)
+	{
+		write_int_raw((DEBUG_TYPE_STR<<DEBUG_TYPE_SHIFT) | storagesize);
+		
+		while(storagesize-1) // make sure to zero terminate
+		{
+			if(!*str)
+				break;
+			
+			*current = *str;
+			current++;
+			str++;
+			storagesize--;
+		}
+		
+		while(storagesize)
+		{
+			*current = 0;
+			current++;
+			storagesize--;
+		}
+	}
+	
+	// reads an int from the packet
+	int read_int()
+	{
+		int t;
+		read_int_raw(&t);
+		dbg_assert(t == ((DEBUG_TYPE_INT<<DEBUG_TYPE_SHIFT) | 4), "error during packet disassembly");
+		read_int_raw(&t);
+		return t;
+	}
+	
+	// reads a string from the packet
+	void read_str(char *str, int storagesize)
+	{
+		int t;
+		read_int_raw(&t);
+		dbg_assert(t == ((DEBUG_TYPE_STR<<DEBUG_TYPE_SHIFT) | storagesize), "error during packet disassembly.");
+		mem_copy(str, current, storagesize);
+		current += storagesize;
+		dbg_assert(str[storagesize-1] == 0, "string should be zero-terminated.");
+	}
+	
+	void read_raw(char *data, int size)
+	{
+		int t;
+		read_int_raw(&t);
+		dbg_assert(t == ((DEBUG_TYPE_RAW<<DEBUG_TYPE_SHIFT) | size), "error during packet disassembly.");
+		while(size--)
+			*data++ = *current++;
+	}
+	
+	// TODO: impelement this
+	bool is_good() const { return true; }
+
+	unsigned msg() const { return ((header*)packet_data)->msg; }
+	unsigned seq() const { return ((header*)packet_data)->seq; }
+	unsigned ack() const { return ((header*)packet_data)->ack; }
+	
+	// access functions to get the size and data
+	int size() const { return (int)(current-(unsigned char*)packet_data); }
+	int max_size() const { return MAX_PACKET_SIZE; }
+	void *data() { return packet_data; }
+};
+
+//
+class connection
+{
+	baselib::socket_udp4 *socket;
+	baselib::netaddr4 addr;
+	unsigned seq;
+	unsigned ack;
+	unsigned last_ack;
+	
+	unsigned counter_sent_bytes;
+	unsigned counter_recv_bytes;
+	
+public:
+	void counter_reset()
+	{
+		counter_sent_bytes = 0;
+		counter_recv_bytes = 0;
+	}
+	
+	void counter_get(unsigned *sent, unsigned *recved)
+	{
+		*sent = counter_sent_bytes;
+		*recved = counter_recv_bytes;
+	}
+
+	void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr)
+	{
+		this->addr = *addr;
+		this->socket = socket;
+		last_ack = 0;
+		ack = 0;
+		seq = 0;
+		counter_reset();
+	}
+	
+	void send(packet *p)
+	{
+		if(p->msg()&(31<<1))
+		{
+			// vital packet
+			seq++;
+			// TODO: save packet, we might need to retransmit
+		}
+		
+		//dbg_msg("network/connection", "sending packet. msg=%x size=%x seq=%x ", p->msg(), p->size(), seq);
+		p->set_header(ack, seq);
+		socket->send(&address(), p->data(), p->size());
+		counter_sent_bytes += p->size();
+	}
+	
+	packet *feed(packet *p)
+	{
+		counter_recv_bytes += p->size();
+		
+		if(p->msg()&(31<<1))
+		{
+			if(p->seq() == ack+1)
+			{
+				// packet in seqence, ack it
+				ack++;
+				//dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack);
+				return p;
+			}
+			else if(p->seq() > ack)
+			{
+				// TODO: request resend
+				// packet loss
+				dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack);
+				return p;
+			}
+			else
+			{
+				// we already got this packet
+				return 0x0;
+			}
+		}
+		
+		if(last_ack != p->ack())
+		{
+			// TODO: remove acked packets
+		}
+		
+		return p;		
+	}
+	
+	const baselib::netaddr4 &address() const { return addr; }
+	
+	void update()
+	{
+	}
+};
+
+//const char *NETWORK_VERSION = "development";
+
+enum
+{
+	NETMSG_VITAL=0x80000000,
+	NETMSG_CONTEXT_CONNECT=0x00010000,
+	NETMSG_CONTEXT_GAME=0x00020000,
+	NETMSG_CONTEXT_GLOBAL=0x00040000,
+	
+	// connection phase
+	NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1,
+		// str32 name
+		// str32 clan
+		// str32 password
+		// str32 skin	
+	
+	// TODO: These should be implemented to make the server send the map to the client on connect
+	// NETMSG_CLIENT_FETCH,
+	// NETMSG_SERVER_MAPDATA,
+	
+	NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2,
+		// str32 mapname
+
+	
+	NETMSG_CLIENT_DONE=NETMSG_VITAL|NETMSG_CONTEXT_CONNECT|3,
+		// nothing
+	
+	// game phase
+	NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these
+		// int num_parts
+		// int part
+		// int size
+		// data *
+		
+	NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these
+		// int input[MAX_INPUTS]
+	
+	NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2,
+	NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|NETMSG_VITAL|2,
+
+	NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|NETMSG_VITAL|3, // check if client is alive
+	
+	NETMSG_CLIENT_ERROR=0x0fffffff,
+		// str128 reason
+		
+	NETMSG_SERVER_ERROR=0x0fffffff,
+		// str128 reason
+};
+
+enum
+{
+	MAX_NAME_LENGTH=32,
+	MAX_CLANNAME_LENGTH=32,
+	MAX_INPUT_SIZE=128,
+	MAX_SNAPSHOT_SIZE=64*1024,
+	MAX_SNAPSHOT_PACKSIZE=768
+};
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 00000000..9783ac5e
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,661 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <baselib/system.h>
+#include <baselib/network.h>
+
+#include "interface.h"
+
+//#include "socket.h"
+#include "packet.h"
+#include "snapshot.h"
+
+#include "lzw.h"
+
+namespace baselib {}
+using namespace baselib;
+
+class snapshot_builder
+{
+public:
+	static const int MAX_ITEMS = 512;
+	//static const int MAX_DATA_SIZE=1*1024;
+
+	char data[MAX_SNAPSHOT_SIZE];
+	int data_size;
+
+	int offsets[MAX_ITEMS];
+	int num_items;
+
+	int top_size;
+	int top_items;
+
+	int snapnum;
+
+	snapshot_builder()
+	{
+		top_size = 0;
+		top_items = 0;
+		snapnum = 0;
+	}
+	
+	void start()
+	{
+		data_size = 0;
+		num_items = 0;
+	}
+	
+	int finish(void *snapdata)
+	{
+		snapnum++;
+		
+		// collect some data
+		/*
+		int change = 0;
+		if(data_size > top_size)
+		{
+			change++;
+			top_size = data_size;
+		}
+		
+		if(num_items > top_items)
+		{
+			change++;
+			top_items = num_items;
+		}
+		
+		if(change)
+		{
+			dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size);
+		}*/
+		
+		// flattern and make the snapshot
+		snapshot *snap = (snapshot *)snapdata;
+		snap->num_items = num_items;
+		int offset_size = sizeof(int)*num_items;
+		mem_copy(snap->offsets, offsets, offset_size);
+		mem_copy(snap->data_start(), data, data_size);
+		return sizeof(int) + offset_size + data_size;
+	}
+	
+	void *new_item(int type, int id, int size)
+	{
+		snapshot::item *obj = (snapshot::item *)(data+data_size);
+		obj->type_and_id = (type<<16)|id;
+		offsets[num_items] = data_size;
+		data_size += sizeof(int) + size;
+		num_items++;
+		dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data");
+		dbg_assert(num_items < MAX_ITEMS, "too many items");
+		
+		return &obj->data;
+	}
+};
+
+static snapshot_builder builder;
+
+void *snap_new_item(int type, int id, int size)
+{
+	dbg_assert(type >= 0 && type <=0xffff, "incorrect type");
+	dbg_assert(id >= 0 && id <=0xffff, "incorrect id");
+	return builder.new_item(type, id, size);
+}
+
+
+//
+class client
+{
+public:
+	enum
+	{
+		STATE_EMPTY = 0,
+		STATE_CONNECTING = 1,
+		STATE_INGAME = 2,
+	};
+
+	// connection state info
+	int state;
+
+	// (ticks) if lastactivity > 5 seconds kick him
+	int64 lastactivity;
+	connection conn;
+	
+	char name[MAX_NAME_LENGTH];
+	char clan[MAX_CLANNAME_LENGTH];
+	/*
+	client()
+	{
+		state = STATE_EMPTY;
+		name[0] = 0;
+		clan[0] = 0;
+	}
+	
+	~client()
+	{
+		dbg_assert(state == STATE_EMPTY, "client destoyed while in use");
+	}*/
+	
+	bool is_empty() const { return state == STATE_EMPTY; }
+	bool is_ingame() const { return state == STATE_INGAME; }
+	const netaddr4 &address() const { return conn.address(); }
+};
+
+static client clients[MAX_CLIENTS];
+static int current_tick = 0;
+static int send_heartbeats = 1;
+
+int server_tick()
+{
+	return current_tick;
+}
+
+int server_tickspeed()
+{
+	return 50;
+}
+
+int server_init()
+{
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+		clients[i].state = client::STATE_EMPTY;
+		clients[i].name[0] = 0;
+		clients[i].clan[0] = 0;
+		clients[i].lastactivity = 0;
+	}
+	
+	current_tick = 0;
+	
+	return 0;
+}
+
+int server_getclientinfo(int client_id, client_info *info)
+{
+	dbg_assert(client_id >= 0 && client_id < MAX_CLIENTS, "client_id is not valid");
+	dbg_assert(info != 0, "info can not be null");
+	
+	if(clients[client_id].is_ingame())
+	{
+		info->name = clients[client_id].name;
+		info->latency = 0;
+		return 1;
+	}
+	return 0;
+}
+
+//
+class server
+{
+public:
+
+	socket_udp4 game_socket;
+	const char *map_name;
+	const char *server_name;
+	int64 lasttick;
+	int64 lastheartbeat;
+	netaddr4 master_server;
+
+	int biggest_snapshot;
+	
+	bool run(const char *servername, const char *mapname)
+	{
+		biggest_snapshot = 0;
+		
+        net_init(); // For Windows compatibility.
+		map_name = mapname;
+		server_name = servername;
+
+		// load map
+		if(!map_load(mapname))
+		{
+			dbg_msg("server", "failed to load map. mapname='%s'");
+			return false;
+		}
+		
+		// start server
+		if(!game_socket.open(8303))
+		{
+			dbg_msg("network/server", "couldn't open socket");
+			return false;
+		}
+
+		for(int i = 0; i < MAX_CLIENTS; i++)
+			dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
+
+		if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0)
+			master_server = netaddr4(0, 0, 0, 0, 0);
+		
+		mods_init();
+		
+		int64 time_per_tick = time_freq()/SERVER_TICK_SPEED;
+		int64 time_per_heartbeat = time_freq() * 30;
+		int64 starttime = time_get();
+		//int64 lasttick = starttime;
+		lasttick = starttime;
+		lastheartbeat = 0;
+
+		int64 reporttime = time_get();
+		int64 reportinterval = time_freq()*3;
+		
+		int64 simulationtime = 0;
+		int64 snaptime = 0;
+		int64 networktime = 0;
+		
+		while(1)
+		{
+			int64 t = time_get();
+			if(t-lasttick > time_per_tick)
+			{
+				{
+					int64 start = time_get();
+					tick();
+					simulationtime += time_get()-start;
+				}
+				
+				{
+					int64 start = time_get();
+					snap();
+					snaptime += time_get()-start;
+				}
+
+				// Check for client timeouts
+				for (int i = 0; i < MAX_CLIENTS; i++)
+				{
+					if (clients[i].state != client::STATE_EMPTY)
+					{
+						// check last activity time
+						if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT)
+							client_timeout(i);
+					}
+				}
+				
+				lasttick += time_per_tick;
+			}
+
+			if(send_heartbeats)
+			{
+				if (t > lastheartbeat+time_per_heartbeat)
+				{
+					if (master_server.port != 0)
+					{
+						int players = 0;
+						
+						for (int i = 0; i < MAX_CLIENTS; i++)
+							if (!clients[i].is_empty())
+								players++;
+						
+						netaddr4 me(127, 0, 0, 0, 8303);
+						
+						send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname);
+					}
+
+					lastheartbeat = t+time_per_heartbeat;
+				}
+			}
+			
+			{
+				int64 start = time_get();
+				pump_network();
+				networktime += time_get()-start;
+			}
+			
+			if(reporttime < time_get())
+			{
+				int64 totaltime = simulationtime+snaptime+networktime;
+				dbg_msg("server/report", "sim=%.02fms snap=%.02fms net=%.02fms total=%.02fms load=%.02f%%",
+					simulationtime/(float)reportinterval*1000,
+					snaptime/(float)reportinterval*1000,
+					networktime/(float)reportinterval*1000,
+					totaltime/(float)reportinterval*1000,
+					(simulationtime+snaptime+networktime)/(float)reportinterval*100.0f);
+
+				unsigned sent_total=0, recv_total=0;
+                for (int i = 0; i < MAX_CLIENTS; i++)
+   	                if (!clients[i].is_empty())
+					{
+						unsigned s,r;
+						clients[i].conn.counter_get(&s,&r);
+						clients[i].conn.counter_reset();
+						sent_total += s;
+						recv_total += r;
+					}
+
+				
+				dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d",
+					biggest_snapshot, sent_total/3, recv_total/3);
+
+				simulationtime = 0;
+				snaptime = 0;
+				networktime = 0;
+				
+				reporttime += reportinterval;
+			}
+			
+			thread_sleep(1);
+		}
+		
+		mods_shutdown();
+		map_unload();
+	}
+	
+	void tick()
+	{
+		current_tick++;
+		mods_tick();
+	}
+
+	void snap()
+	{
+		mods_presnap();
+	
+		for(int i = 0; i < MAX_CLIENTS; i++)
+		{
+			if(clients[i].is_ingame())
+			{
+				char data[MAX_SNAPSHOT_SIZE];
+				char compdata[MAX_SNAPSHOT_SIZE];
+				builder.start();
+				mods_snap(i);
+
+				// finish snapshot
+				int snapshot_size = builder.finish(data);
+				
+				// compress it
+				int compsize = lzw_compress(data, snapshot_size, compdata);
+				snapshot_size = compsize;
+
+				if(snapshot_size > biggest_snapshot)
+					biggest_snapshot = snapshot_size;
+				
+				const int max_size = MAX_SNAPSHOT_PACKSIZE;
+				int numpackets = (snapshot_size+max_size-1)/max_size;
+				for(int n = 0, left = snapshot_size; left; n++)
+				{
+					int chunk = left < max_size ? left : max_size;
+					left -= chunk;
+					
+					packet p(NETMSG_SERVER_SNAP);
+					p.write_int(numpackets);
+					p.write_int(n);
+					p.write_int(chunk);
+					p.write_raw(&compdata[n*max_size], chunk);
+					clients[i].conn.send(&p);
+				}
+			}
+		}
+		
+		mods_postsnap();
+	}	
+
+	void send_accept(client *client, const char *map)
+	{
+		packet p(NETMSG_SERVER_ACCEPT);
+		p.write_str(map, 32);
+		client->conn.send(&p);
+	}
+	
+	void drop(int cid, const char *reason)
+	{
+		if(clients[cid].state == client::STATE_EMPTY)
+			return;
+		
+		clients[cid].state = client::STATE_EMPTY;
+		mods_client_drop(cid);
+		dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name);
+	}
+	
+	int find_client(const netaddr4 *addr)
+	{
+		// fetch client
+		for(int i = 0; i < MAX_CLIENTS; i++)
+		{
+			if(!clients[i].is_empty() && clients[i].address() == *addr)
+				return i;
+		}		
+		return -1;
+	}
+	
+	void client_process_packet(int cid, packet *p)
+	{
+		clients[cid].lastactivity = lasttick;
+		if(p->msg() == NETMSG_CLIENT_DONE)
+		{
+			dbg_msg("game", "player as entered the game. cid=%x", cid);
+			clients[cid].state = client::STATE_INGAME;
+			mods_client_enter(cid);
+		}
+		else if(p->msg() == NETMSG_CLIENT_INPUT)
+		{
+			int input[MAX_INPUT_SIZE];
+			int size = p->read_int();
+			for(int i = 0; i < size/4; i++)
+				input[i] = p->read_int();
+			if(p->is_good())
+			{
+				//dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]);
+				mods_client_input(cid, input);
+			}
+		}
+		else if(p->msg() == NETMSG_CLIENT_ERROR)
+		{
+			char reason[128];
+			p->read_str(reason, 128);
+			if(p->is_good())
+				dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason);
+			else
+				dbg_msg("network/server", "client error. cid=%x", cid);
+			drop(cid, "client error");
+		}
+		else
+		{
+			dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg());
+			drop(cid, "invalid message");
+		}
+	}
+	
+	void process_packet(packet *p, netaddr4 *from)
+	{
+		if(p->msg() == NETMSG_CLIENT_CONNECT)
+		{
+			// we got no state for this client yet
+			char version[32];
+			char name[MAX_NAME_LENGTH];
+			char clan[MAX_CLANNAME_LENGTH];
+			char password[32];
+			char skin[32];
+			
+			p->read_str(version, 32);
+			p->read_str(name, MAX_NAME_LENGTH);
+			p->read_str(clan, MAX_CLANNAME_LENGTH);
+			p->read_str(password, 32);
+			p->read_str(skin, 32);
+			
+			if(p->is_good())
+			{
+				// TODO: check version
+				
+				// look for empty slot, linear search
+				int id = -1;
+				for(int i = 0; i < MAX_CLIENTS; i++)
+					if(clients[i].is_empty())
+					{
+						id = i;
+						break;
+					}
+					
+				if(id != -1)
+				{
+					// slot found
+					mem_copy(clients[id].name, name, MAX_NAME_LENGTH);
+					mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH);
+					clients[id].state = client::STATE_CONNECTING;
+					clients[id].conn.init(&game_socket, from);
+					
+					clients[id].lastactivity = lasttick;
+					clients[id].name[MAX_NAME_LENGTH-1] = 0;
+					clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0;
+					
+					dbg_msg("network/server", "client connected. '%s' on slot %d", name, id);
+					
+					// TODO: return success
+					send_accept(&clients[id], map_name);
+				}
+				else
+				{
+					// no slot found
+					// TODO: send error
+					dbg_msg("network/server", "client connected but server is full");
+
+					for(int i = 0; i < MAX_CLIENTS; i++)
+						dbg_msg("network/server", "\t%d: %d", i, clients[i].state);
+				}
+			}
+		}
+		else
+		{
+			int cid = find_client(from);
+			if(cid >= 0)
+			{
+				if(clients[cid].conn.feed(p))
+				{
+					// packet is ok
+					unsigned msg = p->msg();
+					
+					// client found, check state
+					if(((msg>>16)&0xff)&clients[cid].state)
+					{
+						// state is ok
+						client_process_packet(cid, p);
+					}
+					else
+					{
+						// invalid state, disconnect the client
+						drop(cid, "invalid message at this state");
+					}
+				}
+				else
+				{
+					drop(cid, "connection error");
+				}
+				
+			}
+			else
+				dbg_msg("network/server", "packet from strange address.");
+		}
+	}
+
+	void client_timeout(int clientId)
+	{
+		drop(clientId, "client timedout");
+	}
+	
+	void pump_network()
+	{
+		while(1)
+		{
+			packet p;
+			netaddr4 from;
+			
+			int bytes = game_socket.recv(&from, p.data(), p.max_size());
+			if(bytes <= 0)
+				break;
+			
+			process_packet(&p, &from);
+		}
+		// TODO: check for client timeouts
+	}
+
+	char *write_int(char *buffer, int integer)
+	{
+		*buffer++ = integer >> 24;
+		*buffer++ = integer >> 16;
+		*buffer++ = integer >> 8;
+		*buffer++ = integer;
+
+		return buffer;
+	}
+
+	char *write_netaddr4(char *buffer, netaddr4 *address)
+	{
+		*buffer++ = address->ip[0];
+		*buffer++ = address->ip[1];
+		*buffer++ = address->ip[2];
+		*buffer++ = address->ip[3];
+		
+		return write_int(buffer, address->port);
+	}
+
+	void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name)
+	{
+		char buffer[216] = {0};
+		char *d = buffer;
+
+		d = write_int(d, 'TWHB');
+		d = write_int(d, version);
+		d = write_netaddr4(d, address);
+		d = write_int(d,players);
+		d = write_int(d, max_players);
+		
+		int len = strlen(name);
+		if (len > 128)
+			len = 128;
+
+		memcpy(d, name, len);
+		d += 128;
+
+		len = strlen(map_name);
+		if (len > 64)
+			len = 64;
+
+		memcpy(d, map_name, len);
+		d += 64;
+
+		game_socket.send(&master_server, buffer, sizeof(buffer));
+	}
+};
+
+int server_main(int argc, char **argv)
+{
+	dbg_msg("server", "starting...");
+	
+	const char *mapname = "data/demo.map";
+	const char *servername = 0;
+	// parse arguments
+	for(int i = 1; i < argc; i++)
+	{
+		if(argv[i][0] == '-' && argv[i][1] == 'm' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -m map
+			i++;
+			mapname = argv[i];
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] == 0 && argc - i > 1)
+		{
+			// -n server name
+			i++;
+			servername = argv[i];
+		}
+		else if(argv[i][0] == '-' && argv[i][1] == 'p' && argv[i][2] == 0)
+		{
+			// -p (private server)
+			send_heartbeats = 0;
+		}
+	}
+	
+	if(!mapname)
+	{
+		dbg_msg("server", "no map given (-m MAPNAME)");
+		return 0;
+	}
+
+	if(!servername)
+	{
+		dbg_msg("server", "no server name given (-n \"server name\")");
+		return 0;
+	}
+		
+	server_init();
+	server s;
+	s.run(servername, mapname);
+	return 0;
+}
diff --git a/src/snapshot.h b/src/snapshot.h
new file mode 100644
index 00000000..9d803486
--- /dev/null
+++ b/src/snapshot.h
@@ -0,0 +1,19 @@
+
+struct snapshot
+{
+	int num_items;
+	int offsets[1];
+
+	struct item
+	{
+		int type_and_id;
+		char data[1];
+		
+		int type() { return type_and_id>>16; }
+		int id() { return type_and_id&(0xffff); }
+	};
+	
+	char *data_start() { return (char *)&offsets[num_items]; }
+	item *get_item(int index) { return (item *)(data_start() + offsets[index]); };
+};
+
diff --git a/src/snd.cpp b/src/snd.cpp
new file mode 100644
index 00000000..42997897
--- /dev/null
+++ b/src/snd.cpp
@@ -0,0 +1,519 @@
+#include <baselib/system.h>
+#include <baselib/audio.h>
+#include <baselib/stream/file.h>
+
+#include "interface.h"
+
+using namespace baselib;
+
+static const int NUM_FRAMES_STOP = 512;
+static const float NUM_FRAMES_STOP_INV = 1.0f/(float)NUM_FRAMES_STOP;
+static const int NUM_FRAMES_LERP = 512;
+static const float NUM_FRAMES_LERP_INV = 1.0f/(float)NUM_FRAMES_LERP;
+
+static const float GLOBAL_VOLUME_SCALE = 0.75f;
+
+static const int64 GLOBAL_SOUND_DELAY = 1000;
+
+// --- sound ---
+class sound_data
+{
+public:
+	short *data;
+	int num_samples;
+	int rate;
+	int channels;
+	int sustain_start;
+	int sustain_end;
+	int64 last_played;
+};
+
+inline short clamp(int i)
+{
+	if(i > 0x7fff)
+		return 0x7fff;
+	if(i < -0x7fff)
+		return -0x7fff;
+	return i;
+}
+
+class mixer : public audio_stream
+{
+public:
+	class channel
+	{
+	public:
+		channel()
+		{ data = 0; lerp = -1; stop = -1; }
+		
+		sound_data *data;
+		int tick;
+		int loop;
+		float pan;
+		float vol;
+		float old_vol;
+		float new_vol;
+		int lerp;
+		int stop;
+	};
+	
+	enum
+	{
+		MAX_CHANNELS=8,
+	};
+
+	channel channels[MAX_CHANNELS];
+
+	virtual void fill(void *output, unsigned long frames)
+	{
+		//dbg_msg("snd", "mixing!");
+		
+		short *out = (short*)output;
+		bool clamp_flag = false;
+		
+		int active_channels = 0;
+		for(unsigned long i = 0; i < frames; i++)
+		{
+			int left = 0;
+			int right = 0;
+			
+			for(int c = 0; c < MAX_CHANNELS; c++)
+			{
+				if(channels[c].data)
+				{
+					if(channels[c].data->channels == 1)
+					{
+						left += (1.0f-(channels[c].pan+1.0f)*0.5f) * channels[c].vol * channels[c].data->data[channels[c].tick];
+						right += (channels[c].pan+1.0f)*0.5f * channels[c].vol * channels[c].data->data[channels[c].tick];
+						channels[c].tick++;
+					}
+					else
+					{
+						float pl = channels[c].pan<0.0f?-channels[c].pan:1.0f;
+						float pr = channels[c].pan>0.0f?1.0f-channels[c].pan:1.0f;
+						left += pl*channels[c].vol * channels[c].data->data[channels[c].tick];
+						right += pr*channels[c].vol * channels[c].data->data[channels[c].tick + 1];
+						channels[c].tick += 2;
+					}
+				
+					if(channels[c].loop)
+					{
+						if(channels[c].data->sustain_start >= 0 && channels[c].tick >= channels[c].data->sustain_end)
+							channels[c].tick = channels[c].data->sustain_start;
+						else if(channels[c].tick > channels[c].data->num_samples)
+							channels[c].tick = 0;
+					}
+					else if(channels[c].tick > channels[c].data->num_samples)
+						channels[c].data = 0;
+
+					if(channels[c].stop == 0)
+					{
+						channels[c].stop = -1;
+						channels[c].data = 0;
+					}
+					else if(channels[c].stop > 0)
+					{
+						channels[c].vol = channels[c].old_vol * (float)channels[c].stop * NUM_FRAMES_STOP_INV;
+						channels[c].stop--;
+					}
+					if(channels[c].lerp > 0)
+					{
+						channels[c].vol = (1.0f - (float)channels[c].lerp * NUM_FRAMES_LERP_INV) * channels[c].new_vol +
+							(float)channels[c].lerp * NUM_FRAMES_LERP_INV * channels[c].old_vol;
+						channels[c].lerp--;
+					}
+					active_channels++;
+				}
+			}
+
+			// TODO: remove these
+
+			*out = clamp(left); // left
+			if(*out != left) clamp_flag = true;
+			out++;
+			*out = clamp(right); // right
+			if(*out != right) clamp_flag = true;
+			out++;
+		}
+
+		if(clamp_flag)
+			dbg_msg("snd", "CLAMPED!");
+	}
+	
+	int play(sound_data *sound, unsigned loop, float vol, float pan)
+	{
+		if(time_get() - sound->last_played < GLOBAL_SOUND_DELAY)
+			return -1;
+
+		for(int c = 0; c < MAX_CHANNELS; c++)
+		{
+			if(channels[c].data == 0)
+			{
+				channels[c].data = sound;
+				channels[c].tick = 0;
+				channels[c].loop = loop;
+				channels[c].vol = vol * GLOBAL_VOLUME_SCALE;
+				channels[c].pan = pan;
+				sound->last_played = time_get();
+				return c;
+			}
+		}
+
+		return -1;
+	}
+
+	void stop(int id)
+	{
+		dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
+		channels[id].old_vol = channels[id].vol;
+		channels[id].stop = NUM_FRAMES_STOP;
+	}
+
+	void set_vol(int id, float vol)
+	{
+		dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
+		channels[id].new_vol = vol * GLOBAL_VOLUME_SCALE;
+		channels[id].old_vol = channels[id].vol;
+		channels[id].lerp = NUM_FRAMES_LERP;
+	}
+};
+
+static mixer mixer;
+//static sound_data test_sound;
+
+extern "C" 
+{
+#include "wavpack/wavpack.h"
+}
+
+/*
+static file_stream *read_func_filestream;
+static int32_t read_func(void *buff, int32_t bcount)
+{
+    return read_func_filestream->read(buff, bcount);
+}
+static uchar *format_samples(int bps, uchar *dst, int32_t *src, uint32_t samcnt)
+{
+    int32_t temp;
+
+    switch (bps) {
+
+        case 1:
+            while (samcnt--)
+                *dst++ = *src++ + 128;
+
+            break;
+
+        case 2:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+            }
+
+            break;
+
+        case 3:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+                *dst++ = (uchar)(temp >> 16);
+            }
+
+            break;
+
+        case 4:
+            while (samcnt--) {
+                *dst++ = (uchar)(temp = *src++);
+                *dst++ = (uchar)(temp >> 8);
+                *dst++ = (uchar)(temp >> 16);
+                *dst++ = (uchar)(temp >> 24);
+            }
+
+            break;
+    }
+
+    return dst;
+}*/
+
+/*
+struct sound_holder
+{
+	sound_data sound;
+	int next;
+};
+
+static const int MAX_SOUNDS = 256;
+static sound_holder sounds[MAX_SOUNDS];
+static int first_free_sound;
+
+bool snd_load_wv(const char *filename, sound_data *snd)
+{
+	// open file
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("sound/wv", "failed to open file. filename='%s'", filename);
+		return false;
+	}
+	read_func_filestream = &file;
+	
+	// get info
+	WavpackContext *wpc;
+	char error[128];
+	wpc = WavpackOpenFileInput(read_func, error);
+	if(!wpc)
+	{
+		dbg_msg("sound/wv", "failed to open file. err=%s filename='%s'", error, filename);
+		return false;
+	}
+
+
+	snd->num_samples = WavpackGetNumSamples(wpc);
+    int bps = WavpackGetBytesPerSample(wpc);
+	int channels = WavpackGetReducedChannels(wpc);
+	snd->rate = WavpackGetSampleRate(wpc);
+	int bits = WavpackGetBitsPerSample(wpc);
+	
+	(void)bps;
+	(void)channels;
+	(void)bits;
+	
+	// decompress
+	int datasize = snd->num_samples*2;
+	snd->data = (short*)mem_alloc(datasize, 1);
+	int totalsamples = 0;
+	while(1)
+	{
+		int buffer[1024*4];
+		int samples_unpacked = WavpackUnpackSamples(wpc, buffer, 1024*4);
+		totalsamples += samples_unpacked;
+		
+		if(samples_unpacked)
+		{
+			// convert
+		}
+	}
+	
+	if(snd->num_samples != totalsamples)
+	{
+		dbg_msg("sound/wv", "wrong amount of samples. filename='%s'", filename);
+		mem_free(snd->data);
+		return false;;
+	}
+		
+	return false;
+}*/
+
+struct sound_holder
+{
+	sound_data sound;
+	int next;
+};
+
+static const int MAX_SOUNDS = 1024;
+static sound_holder sounds[MAX_SOUNDS];
+static int first_free_sound;
+
+bool snd_init()
+{
+	first_free_sound = 0;
+	for(int i = 0; i < MAX_SOUNDS; i++)
+		sounds[i].next = i+1;
+	sounds[MAX_SOUNDS-1].next = -1;
+	return mixer.create();
+}
+
+bool snd_shutdown()
+{
+	mixer.destroy();
+	return true;
+}
+
+static int snd_alloc_sound()
+{
+	if(first_free_sound < 0)
+		return -1;
+	int id = first_free_sound;
+	first_free_sound = sounds[id].next;
+	sounds[id].next = -1;
+	return id;
+}
+
+int snd_load_wav(const char *filename)
+{
+	sound_data snd;
+	
+	// open file for reading
+	file_stream file;
+	if(!file.open_r(filename))
+	{
+		dbg_msg("sound/wav", "failed to open file. filename='%s'", filename);
+		return -1;
+	}
+
+	int id = -1;
+	int state = 0;
+	while(1)
+	{
+		// read chunk header
+		unsigned char head[8];
+		if(file.read(head, sizeof(head)) != 8)
+		{
+			break;
+		}
+		
+		int chunk_size = head[4] | (head[5]<<8) | (head[6]<<16) | (head[7]<<24);
+		head[4] = 0;
+			
+		if(state == 0)
+		{
+			// read the riff and wave headers
+			if(head[0] != 'R' || head[1] != 'I' || head[2] != 'F' || head[3] != 'F')
+			{
+				dbg_msg("sound/wav", "not a RIFF file. filename='%s'", filename);
+				return -1;
+			}
+			
+			unsigned char type[4];
+			file.read(type, 4);
+
+			if(type[0] != 'W' || type[1] != 'A' || type[2] != 'V' || type[3] != 'E')
+			{
+				dbg_msg("sound/wav", "RIFF file is not a WAVE. filename='%s'", filename);
+				return -1;
+			}
+			
+			state++;
+		}
+		else if(state == 1)
+		{
+			// read the format chunk
+			if(head[0] == 'f' && head[1] == 'm' && head[2] == 't' && head[3] == ' ')
+			{
+				unsigned char fmt[16];
+				if(file.read(fmt, sizeof(fmt)) !=  sizeof(fmt))
+				{
+					dbg_msg("sound/wav", "failed to read format. filename='%s'", filename);
+					return -1;
+				}
+				
+				// decode format
+				int compression_code = fmt[0] | (fmt[1]<<8);
+				snd.channels = fmt[2] | (fmt[3]<<8);
+				snd.rate = fmt[4] | (fmt[5]<<8) | (fmt[6]<<16) | (fmt[7]<<24);
+
+				if(compression_code != 1)
+				{
+					dbg_msg("sound/wav", "file is not uncompressed. filename='%s'", filename);
+					return -1;
+				}
+				
+				if(snd.channels > 2)
+				{
+					dbg_msg("sound/wav", "file is not mono or stereo. filename='%s'", filename);
+					return -1;
+				}
+
+				if(snd.rate != 44100)
+				{
+					dbg_msg("sound/wav", "file is %d Hz, not 44100 Hz. filename='%s'", snd.rate, filename);
+					return -1;
+				}
+				
+				int bps = fmt[14] | (fmt[15]<<8);
+				if(bps != 16)
+				{
+					dbg_msg("sound/wav", "bps is %d, not 16, filname='%s'", bps, filename);
+					return -1;
+				}
+				
+				// skip extra bytes (not used for uncompressed)
+				//int extra_bytes = fmt[14] | (fmt[15]<<8);
+				//dbg_msg("sound/wav", "%d", extra_bytes);
+				//file.skip(extra_bytes);
+				
+				// next state
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else if(state == 2)
+		{
+			// read the data
+			if(head[0] == 'd' && head[1] == 'a' && head[2] == 't' && head[3] == 'a')
+			{
+				snd.data = (short*)mem_alloc(chunk_size, 1);
+				file.read(snd.data, chunk_size);
+				snd.num_samples = chunk_size/(2);
+				snd.sustain_start = -1;
+				snd.sustain_end = -1;
+				snd.last_played = 0;
+				id = snd_alloc_sound();
+				sounds[id].sound = snd;
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else if(state == 3)
+		{
+			if(head[0] == 's' && head[1] == 'm' && head[2] == 'p' && head[3] == 'l')
+			{
+				int smpl[9];
+				int loop[6];
+
+				file.read(smpl, sizeof(smpl));
+
+				if(smpl[7] > 0)
+				{
+					file.read(loop, sizeof(loop));
+					sounds[id].sound.sustain_start = loop[2] * sounds[id].sound.channels;
+					sounds[id].sound.sustain_end = loop[3] * sounds[id].sound.channels;
+				}
+
+				if(smpl[7] > 1)
+					file.skip((smpl[7]-1) * sizeof(loop));
+
+				file.skip(smpl[8]);
+				state++;
+			}
+			else
+				file.skip(chunk_size);
+		}
+		else
+			file.skip(chunk_size);
+	}
+
+	if(id >= 0)
+		dbg_msg("sound/wav", "loaded %s", filename);
+	else
+		dbg_msg("sound/wav", "failed to load %s", filename);
+
+	return id;
+}
+
+int snd_play(int id, int loop, float vol, float pan)
+{
+	if(id < 0)
+	{
+		dbg_msg("snd", "bad sound id");
+		return -1;
+	}
+
+	dbg_assert(sounds[id].sound.data != 0, "null sound");
+	dbg_assert(sounds[id].next == -1, "sound isn't allocated");
+	return mixer.play(&sounds[id].sound, loop, vol, pan);
+}
+
+void snd_stop(int id)
+{
+	if(id >= 0)
+		mixer.stop(id);
+}
+
+void snd_set_vol(int id, float vol)
+{
+	if(id >= 0)
+		mixer.set_vol(id, vol);
+}
diff --git a/src/ui.cpp b/src/ui.cpp
new file mode 100644
index 00000000..7ef19b72
--- /dev/null
+++ b/src/ui.cpp
@@ -0,0 +1,115 @@
+#include "interface.h"
+#include "ui.h"
+
+/********************************************************
+ UI                                                      
+*********************************************************/
+//static unsigned mouse_buttons_last = 0;
+
+struct pretty_font
+{
+    char m_CharStartTable[256];
+	char m_CharEndTable[256];
+	int font_texture;
+};
+
+extern pretty_font *current_font;
+void gfx_pretty_text(float x, float y, float size, const char *text);
+
+
+static void *hot_item = 0;
+static void *active_item = 0;
+static void *becomming_hot_item = 0;
+static float mouse_x, mouse_y; // in gui space
+static float mouse_wx, mouse_wy; // in world space
+static unsigned mouse_buttons = 0;
+
+float ui_mouse_x() { return mouse_x; }
+float ui_mouse_y() { return mouse_y; }
+float ui_mouse_world_x() { return mouse_wx; }
+float ui_mouse_world_y() { return mouse_wy; }
+int ui_mouse_button(int index) { return (mouse_buttons>>index)&1; }
+
+void ui_set_hot_item(void *id) { becomming_hot_item = id; }
+void ui_set_active_item(void *id) { active_item = id; }
+void *ui_hot_item() { return hot_item; }
+void *ui_active_item() { return active_item; }
+
+int ui_update(float mx, float my, float mwx, float mwy, int buttons)
+{
+    //mouse_buttons_last = mouse_buttons;
+    mouse_x = mx;
+    mouse_y = my;
+    mouse_wx = mwx;
+    mouse_wy = mwy;
+    mouse_buttons = buttons;
+    hot_item = becomming_hot_item;
+    becomming_hot_item = 0;
+    return 0;
+}
+
+/*
+static int ui_mouse_button_released(int index)
+{
+    return ((mouse_buttons_last>>index)&1) && !();
+}*/
+
+int ui_mouse_inside(float x, float y, float w, float h)
+{
+    if(mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h)
+        return 1;
+    return 0;
+}
+
+void ui_do_image(int texture, float x, float y, float w, float h)
+{
+    gfx_blend_normal();
+    gfx_texture_set(texture);
+    gfx_quads_begin();
+    gfx_quads_setcolor(1,1,1,1);
+    gfx_quads_setsubset(
+        0.0f, // startx
+        0.0f, // starty
+        1.0f, // endx
+        1.0f); // endy                                
+    gfx_quads_drawTL(x,y,w,h);
+    gfx_quads_end();
+}
+
+void ui_do_label(float x, float y, char *text)
+{
+    gfx_blend_normal();
+    gfx_texture_set(current_font->font_texture);
+    gfx_pretty_text(x, y, 18.f, text);
+}
+
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra)
+{
+    // logic
+    int r = 0;
+    int inside = ui_mouse_inside(x,y,w,h);
+
+	if(inside)
+	{
+		ui_set_hot_item(id);
+
+		if(ui_mouse_button(0))
+			ui_set_active_item(id);
+	}
+
+	if(ui_active_item() == id && ui_hot_item() == id && !ui_mouse_button(0))
+	{
+		ui_set_active_item(0);
+		r = 1;
+	}
+
+    draw_func(id, text, checked, x, y, w, h, extra);
+
+    return r;
+}
+
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func)
+{
+	return ui_do_button(id, text, checked, x, y, w, h, draw_func, 0x0);
+}
+
diff --git a/src/ui.h b/src/ui.h
new file mode 100644
index 00000000..1a420906
--- /dev/null
+++ b/src/ui.h
@@ -0,0 +1,33 @@
+#ifndef _UI_H
+#define _UI_H
+/*
+extern void *hot_item;
+extern void *active_item;
+extern void *becomming_hot_item;
+extern float mouse_x, mouse_y; // in gui space
+extern float mouse_wx, mouse_wy; // in world space
+extern unsigned mouse_buttons;*/
+
+int ui_update(float mx, float my, float mwx, float mwy, int buttons);
+
+float ui_mouse_x();
+float ui_mouse_y();
+float ui_mouse_world_x();
+float ui_mouse_world_y();
+int ui_mouse_button(int index);
+
+void ui_set_hot_item(void *id);
+void ui_set_active_item(void *id);
+void *ui_hot_item();
+void *ui_active_item();
+
+int ui_mouse_inside(float x, float y, float w, float h);
+
+typedef void (*draw_button_callback)(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra);
+
+void ui_do_image(int texture, float x, float y, float w, float h);
+void ui_do_label(float x, float y, char *text);
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func, void *extra);
+int ui_do_button(void *id, const char *text, int checked, float x, float y, float w, float h, draw_button_callback draw_func);
+
+#endif
diff --git a/src/wavpack/arm.S b/src/wavpack/arm.S
new file mode 100644
index 00000000..ab882181
--- /dev/null
+++ b/src/wavpack/arm.S
@@ -0,0 +1,461 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+/* This is an assembly optimized version of the following WavPack function:

+ *

+ * void decorr_stereo_pass_cont (struct decorr_pass *dpp,

+ *                               long *buffer, long sample_count);

+ *

+ * It performs a single pass of stereo decorrelation on the provided buffer.

+ * Note that this version of the function requires that the 8 previous stereo

+ * samples are visible and correct. In other words, it ignores the "samples_*"

+ * fields in the decorr_pass structure and gets the history data directly

+ * from the buffer. It does, however, return the appropriate history samples

+ * to the decorr_pass structure before returning.

+ *

+ * This is written to work on a ARM7TDMI processor. This version only uses the

+ * 32-bit multiply-accumulate instruction and so will overflow with 24-bit

+ * WavPack files.

+ */

+        .text

+        .align

+        .global         decorr_stereo_pass_cont_arm

+

+/*

+ * on entry:

+ *

+ * r0 = struct decorr_pass *dpp

+ * r1 = long *buffer

+ * r2 = long sample_count

+ */

+

+decorr_stereo_pass_cont_arm:

+

+        stmfd   sp!, {r4 - r8, r10, r11, lr}

+        mov     r5, r0                  @ r5 = dpp

+        mov     r11, #512               @ r11 = 512 for rounding

+        ldrsh   r6, [r0, #2]            @ r6 = dpp->delta

+        ldrsh   r4, [r0, #4]            @ r4 = dpp->weight_A

+        ldrsh   r0, [r0, #6]            @ r0 = dpp->weight_B

+        cmp     r2, #0                  @ exit if no samples to process

+        beq     common_exit

+

+        add     r7, r1, r2, asl #3      @ r7 = buffer ending position

+        ldrsh   r2, [r5, #0]            @ r2 = dpp->term

+        cmp     r2, #0

+        bmi     minus_term

+

+        ldr     lr, [r1, #-16]          @ load 2 sample history from buffer

+        ldr     r10, [r1, #-12]         @  for terms 2, 17, and 18

+        ldr     r8, [r1, #-8]

+        ldr     r3, [r1, #-4]

+        cmp     r2, #17

+        beq     term_17_loop

+        cmp     r2, #18

+        beq     term_18_loop

+        cmp     r2, #2

+        beq     term_2_loop

+        b       term_default_loop       @ else handle default (1-8, except 2)

+

+minus_term:

+        mov     r10, #1024              @ r10 = -1024 for weight clipping

+        rsb     r10, r10, #0            @  (only used for negative terms)

+        cmn     r2, #1

+        beq     term_minus_1

+        cmn     r2, #2

+        beq     term_minus_2

+        cmn     r2, #3

+        beq     term_minus_3

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 17 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample

+ * r3 = previous right sample   r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = current decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_17_loop:

+        rsbs    ip, lr, r8, asl #1      @ decorr value = (2 * prev) - 2nd prev

+        mov     lr, r8                  @ previous becomes 2nd previous

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mla     r8, ip, r4, r11         @ mult decorr value by weight, round,

+        add     r8, r2, r8, asr #10     @  shift, and add to new sample

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L325

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L325:  rsbs    ip, r10, r3, asl #1     @ do same thing for right channel

+        mov     r10, r3

+        ldr     r2, [r1], #4

+        mla     r3, ip, r0, r11

+        add     r3, r2, r3, asr #10

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L329

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L329:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_17_loop

+        b       store_1718              @ common exit for terms 17 & 18

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 18 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample

+ * r3 = previous right sample   r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_18_loop:

+        sub     ip, r8, lr              @ decorr value =

+        mov     lr, r8                  @  ((3 * prev) - 2nd prev) >> 1

+        adds    ip, r8, ip, asr #1

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mla     r8, ip, r4, r11         @ mult decorr value by weight, round,

+        add     r8, r2, r8, asr #10     @  shift, and add to new sample

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L337

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L337:  sub     ip, r3, r10             @ do same thing for right channel

+        mov     r10, r3

+        adds    ip, r3, ip, asr #1

+        ldr     r2, [r1], #4

+        mla     r3, ip, r0, r11

+        add     r3, r2, r3, asr #10

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L341

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L341:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_18_loop

+

+/* common exit for terms 17 & 18 */

+

+store_1718:

+        str     r3, [r5, #40]           @ store sample history into struct

+        str     r8, [r5, #8]

+        str     r10, [r5, #44]

+        str     lr, [r5, #12]

+        b       common_exit             @ and return

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 2 condition

+ * (note that this case can be handled by the default term handler (1-8), but

+ * this special case is faster because it doesn't have to read memory twice)

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample

+ * r3 = previous right sample   r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_2_loop:

+        movs    ip, lr                  @ get decorrelation value & test

+        mov     lr, r8                  @ previous becomes 2nd previous

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mla     r8, ip, r4, r11         @ mult decorr value by weight, round,

+        add     r8, r2, r8, asr #10     @  shift, and add to new sample

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L225

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L225:  movs    ip, r10                 @ do same thing for right channel

+        mov     r10, r3

+        ldr     r2, [r1], #4

+        mla     r3, ip, r0, r11

+        add     r3, r2, r3, asr #10

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L229

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L229:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_2_loop

+        b       default_term_exit       @ this exit updates all dpp->samples

+

+/*

+ ******************************************************************************

+ * Loop to handle default term condition

+ *

+ * r0 = dpp->weight_B           r8 = result accumulator

+ * r1 = bptr                    r9 = 

+ * r2 = dpp->term               r10 =

+ * r3 = decorrelation value     r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr =

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_default_loop:

+        ldr     ip, [r1]                @ get original sample

+        ldr     r3, [r1, -r2, asl #3]   @ get decorrelation value based on term

+        mla     r8, r3, r4, r11         @ mult decorr value by weight, round,

+        add     r8, ip, r8, asr #10     @  shift and add to new sample

+        str     r8, [r1], #4            @ store update sample

+        cmp     r3, #0

+        cmpne   ip, #0

+        beq     .L350

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L350:  ldr     ip, [r1]                @ do the same thing for right channel

+        ldr     r3, [r1, -r2, asl #3]

+        mla     r8, r3, r0, r11

+        add     r8, ip, r8, asr #10

+        str     r8, [r1], #4

+        cmp     r3, #0

+        cmpne   ip, #0

+        beq     .L354

+        teq     ip, r3

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L354:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_default_loop

+

+/*

+ * This exit is used by terms 1-8 to store the previous 8 samples into the decorr

+ * structure (even if they are not all used for the given term)

+ */

+

+default_term_exit:

+        ldrsh   r3, [r5, #0]

+        sub     ip, r3, #1

+        mov     lr, #7

+

+.L358:  and     r3, ip, #7

+        add     r3, r5, r3, asl #2

+        ldr     r2, [r1, #-4]

+        str     r2, [r3, #40]

+        ldr     r2, [r1, #-8]!

+        str     r2, [r3, #8]

+        sub     ip, ip, #1

+        sub     lr, lr, #1

+        cmn     lr, #1

+        bne     .L358

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -1 condition

+ *

+ * r0 = dpp->weight_B           r8 =

+ * r1 = bptr                    r9 = 

+ * r2 = intermediate result     r10 = -1024 (for clipping)

+ * r3 = previous right sample   r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = updated left sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_1:

+        ldr     r3, [r1, #-4]

+

+term_minus_1_loop:

+        ldr     ip, [r1]                @ for left channel the decorrelation value

+        mla     r2, r3, r4, r11         @  is the previous right sample (in r3)

+        add     lr, ip, r2, asr #10

+        str     lr, [r1], #8

+        cmp     r3, #0

+        cmpne   ip, #0

+        beq     .L361

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #1024

+        movgt   r4, #1024

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L361:  ldr     r2, [r1, #-4]           @ for right channel the decorrelation value

+        mla     r3, lr, r0, r11         @  is the just updated right sample (in lr)

+        add     r3, r2, r3, asr #10

+        str     r3, [r1, #-4]

+        cmp     lr, #0

+        cmpne   r2, #0

+        beq     .L369

+        teq     r2, lr

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #1024               @ then clip weight to +/-1024

+        movgt   r0, #1024

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L369:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_1_loop

+

+        str     r3, [r5, #8]            @ else store right sample and exit

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -2 condition

+ * (note that the channels are processed in the reverse order here)

+ *

+ * r0 = dpp->weight_B           r8 =

+ * r1 = bptr                    r9 = 

+ * r2 = intermediate result     r10 = -1024 (for clipping)

+ * r3 = previous left sample    r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = updated right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_2:

+        ldr     r3, [r1, #-8]

+

+term_minus_2_loop:

+        ldr     ip, [r1, #4]            @ for right channel the decorrelation value

+        mla     r2, r3, r0, r11         @  is the previous left sample (in r3)

+        add     lr, ip, r2, asr #10

+        str     lr, [r1, #4]

+        cmp     r3, #0

+        cmpne   ip, #0

+        beq     .L380

+        teq     ip, r3                  @ update weight based on signs

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #1024               @ then clip weight to +/-1024

+        movgt   r0, #1024

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L380:  ldr     r2, [r1, #0]            @ for left channel the decorrelation value

+        mla     r3, lr, r4, r11         @  is the just updated left sample (in lr)

+        add     r3, r2, r3, asr #10

+        str     r3, [r1], #8

+        cmp     lr, #0

+        cmpne   r2, #0

+        beq     .L388

+        teq     r2, lr

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #1024

+        movgt   r4, #1024

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L388:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_2_loop

+

+        str     r3, [r5, #40]           @ else store left channel and exit

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -3 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current left sample     r10 = -1024 (for clipping)

+ * r3 = previous right sample   r11 = 512 (for rounding)

+ * r4 = dpp->weight_A           ip = intermediate result

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr =

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_3:

+        ldr     r3, [r1, #-4]           @ load previous samples

+        ldr     r8, [r1, #-8]

+

+term_minus_3_loop:

+        ldr     ip, [r1]

+        mla     r2, r3, r4, r11

+        add     r2, ip, r2, asr #10

+        str     r2, [r1], #4

+        cmp     r3, #0

+        cmpne   ip, #0

+        beq     .L399

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #1024               @ then clip weight to +/-1024

+        movgt   r4, #1024

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L399:  movs    ip, r8                  @ ip = previous left we use now

+        mov     r8, r2                  @ r8 = current left we use next time

+        ldr     r2, [r1], #4

+        mla     r3, ip, r0, r11

+        add     r3, r2, r3, asr #10

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L407

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #1024

+        movgt   r0, #1024

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L407:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_3_loop

+

+        str     r3, [r5, #8]            @ else store previous samples & exit

+        str     r8, [r5, #40]

+

+/*

+ * Before finally exiting we must store weights back for next time

+ */

+

+common_exit:

+        strh    r4, [r5, #4]

+        strh    r0, [r5, #6]

+        ldmfd   sp!, {r4 - r8, r10, r11, pc}

+

diff --git a/src/wavpack/arml.S b/src/wavpack/arml.S
new file mode 100644
index 00000000..39de5383
--- /dev/null
+++ b/src/wavpack/arml.S
@@ -0,0 +1,491 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+/* This is an assembly optimized version of the following WavPack function:

+ *

+ * void decorr_stereo_pass_cont (struct decorr_pass *dpp,

+ *                               long *buffer, long sample_count);

+ *

+ * It performs a single pass of stereo decorrelation on the provided buffer.

+ * Note that this version of the function requires that the 8 previous stereo

+ * samples are visible and correct. In other words, it ignores the "samples_*"

+ * fields in the decorr_pass structure and gets the history data directly

+ * from the buffer. It does, however, return the appropriate history samples

+ * to the decorr_pass structure before returning.

+ *

+ * This is written to work on a ARM7TDMI processor. This version uses the

+ * 64-bit multiply-accumulate instruction and so can be used with all

+ * WavPack files. However, for optimum performance with 16-bit WavPack

+ * files, there is a faster version that only uses the 32-bit MLA

+ * instruction.

+ */

+

+        .text

+        .align

+        .global         decorr_stereo_pass_cont_arml

+

+/*

+ * on entry:

+ *

+ * r0 = struct decorr_pass *dpp

+ * r1 = long *buffer

+ * r2 = long sample_count

+ */

+

+decorr_stereo_pass_cont_arml:

+

+        stmfd   sp!, {r4 - r8, r10, r11, lr}

+        mov     r5, r0                  @ r5 = dpp

+        mov     r11, #512               @ r11 = 512 for rounding

+        ldrsh   r6, [r0, #2]            @ r6 = dpp->delta

+        ldrsh   r4, [r0, #4]            @ r4 = dpp->weight_A

+        ldrsh   r0, [r0, #6]            @ r0 = dpp->weight_B

+        cmp     r2, #0                  @ exit if no samples to process

+        beq     common_exit

+

+        mov     r0, r0, asl #18         @ for 64-bit math we use weights << 18

+        mov     r4, r4, asl #18

+        mov     r6, r6, asl #18

+        add     r7, r1, r2, asl #3      @ r7 = buffer ending position

+        ldrsh   r2, [r5, #0]            @ r2 = dpp->term

+        cmp     r2, #0

+        blt     minus_term

+

+        ldr     lr, [r1, #-16]          @ load 2 sample history from buffer

+        ldr     r10, [r1, #-12]         @  for terms 2, 17, and 18

+        ldr     r8, [r1, #-8]

+        ldr     r3, [r1, #-4]

+

+        cmp     r2, #18

+        beq     term_18_loop

+        mov     lr, lr, asl #4

+        mov     r10, r10, asl #4

+        cmp     r2, #2

+        beq     term_2_loop

+        cmp     r2, #17

+        beq     term_17_loop

+        b       term_default_loop

+

+minus_term:

+        mov     r10, #(1024 << 18)      @ r10 = -1024 << 18 for weight clipping

+        rsb     r10, r10, #0            @  (only used for negative terms)

+        cmn     r2, #1

+        beq     term_minus_1

+        cmn     r2, #2

+        beq     term_minus_2

+        cmn     r2, #3

+        beq     term_minus_3

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 17 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample << 4

+ * r3 = previous right sample   r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = current decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample << 4

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_17_loop:

+        rsbs    ip, lr, r8, asl #5      @ decorr value = (2 * prev) - 2nd prev

+        mov     lr, r8, asl #4          @ previous becomes 2nd previous

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mov     r11, #0x80000000

+        mov     r8, r2

+        smlalne r11, r8, r4, ip

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L325

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L325:  rsbs    ip, r10, r3, asl #5     @ do same thing for right channel

+        mov     r10, r3, asl #4

+        ldr     r2, [r1], #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r0, ip

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L329

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L329:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_17_loop

+        mov     lr, lr, asr #4

+        mov     r10, r10, asr #4

+        b       store_1718              @ common exit for terms 17 & 18

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 18 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample

+ * r3 = previous right sample   r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_18_loop:

+        rsb     ip, lr, r8              @ decorr value =

+        mov     lr, r8                  @  ((3 * prev) - 2nd prev) >> 1

+        add     ip, lr, ip, asr #1

+        movs    ip, ip, asl #4

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mov     r11, #0x80000000

+        mov     r8, r2

+        smlalne r11, r8, r4, ip

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L337

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L337:  rsb     ip, r10, r3             @ do same thing for right channel

+        mov     r10, r3

+        add     ip, r10, ip, asr #1

+        movs    ip, ip, asl #4

+        ldr     r2, [r1], #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r0, ip

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L341

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L341:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_18_loop

+

+/* common exit for terms 17 & 18 */

+

+store_1718:

+        str     r3, [r5, #40]           @ store sample history into struct

+        str     r8, [r5, #8]

+        str     r10, [r5, #44]

+        str     lr, [r5, #12]

+        b       common_exit             @ and return

+

+/*

+ ******************************************************************************

+ * Loop to handle term = 2 condition

+ * (note that this case can be handled by the default term handler (1-8), but

+ * this special case is faster because it doesn't have to read memory twice)

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current sample          r10 = second previous left sample << 4

+ * r3 = previous right sample   r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = decorrelation value

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = second previous right sample << 4

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_2_loop:

+        movs    ip, lr                  @ get decorrelation value & test

+        ldr     r2, [r1], #4            @ get sample & update pointer

+        mov     lr, r8, asl #4          @ previous becomes 2nd previous

+        mov     r11, #0x80000000

+        mov     r8, r2

+        smlalne r11, r8, r4, ip

+        strne   r8, [r1, #-4]           @ if change possible, store sample back

+        cmpne   r2, #0

+        beq     .L225

+        teq     ip, r2                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L225:  movs    ip, r10                 @ do same thing for right channel

+        ldr     r2, [r1], #4

+        mov     r10, r3, asl #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r0, ip

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L229

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L229:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_2_loop

+

+        b       default_term_exit       @ this exit updates all dpp->samples

+

+/*

+ ******************************************************************************

+ * Loop to handle default term condition

+ *

+ * r0 = dpp->weight_B           r8 = result accumulator

+ * r1 = bptr                    r9 = 

+ * r2 = dpp->term               r10 =

+ * r3 = decorrelation value     r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr =

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_default_loop:

+        ldr     r3, [r1, -r2, asl #3]   @ get decorrelation value based on term

+        ldr     ip, [r1], #4            @ get original sample and bump ptr

+        movs    r3, r3, asl #4

+        mov     r11, #0x80000000

+        mov     r8, ip

+        smlalne r11, r8, r4, r3

+        strne   r8, [r1, #-4]           @ if possibly changed, store updated sample

+        cmpne   ip, #0

+        beq     .L350

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+

+.L350:  ldr     r3, [r1, -r2, asl #3]   @ do the same thing for right channel

+        ldr     ip, [r1], #4

+        movs    r3, r3, asl #4

+        mov     r11, #0x80000000

+        mov     r8, ip

+        smlalne r11, r8, r0, r3

+        strne   r8, [r1, #-4]

+        cmpne   ip, #0

+        beq     .L354

+        teq     ip, r3

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+

+.L354:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_default_loop

+

+/*

+ * This exit is used by terms 1-8 to store the previous 8 samples into the decorr

+ * structure (even if they are not all used for the given term)

+ */

+

+default_term_exit:

+        ldrsh   r3, [r5, #0]

+        sub     ip, r3, #1

+        mov     lr, #7

+

+.L358:  and     r3, ip, #7

+        add     r3, r5, r3, asl #2

+        ldr     r2, [r1, #-4]

+        str     r2, [r3, #40]

+        ldr     r2, [r1, #-8]!

+        str     r2, [r3, #8]

+        sub     ip, ip, #1

+        sub     lr, lr, #1

+        cmn     lr, #1

+        bne     .L358

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -1 condition

+ *

+ * r0 = dpp->weight_B           r8 =

+ * r1 = bptr                    r9 = 

+ * r2 = intermediate result     r10 = -1024 (for clipping)

+ * r3 = previous right sample   r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = updated left sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_1:

+        ldr     r3, [r1, #-4]

+

+term_minus_1_loop:

+        ldr     ip, [r1], #8            @ for left channel the decorrelation value

+        movs    r3, r3, asl #4          @  is the previous right sample (in r3)

+        mov     r11, #0x80000000

+        mov     lr, ip

+        smlalne r11, lr, r4, r3

+        strne   lr, [r1, #-8]

+        cmpne   ip, #0

+        beq     .L361

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #(1024 << 18)

+        movgt   r4, #(1024 << 18)

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L361:  ldr     r2, [r1, #-4]           @ for right channel the decorrelation value

+        movs    lr, lr, asl #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r0, lr

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L369

+        teq     r2, lr

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #(1024 << 18)               @ then clip weight to +/-1024

+        movgt   r0, #(1024 << 18)

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L369:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_1_loop

+

+        str     r3, [r5, #8]            @ else store right sample and exit

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -2 condition

+ * (note that the channels are processed in the reverse order here)

+ *

+ * r0 = dpp->weight_B           r8 =

+ * r1 = bptr                    r9 = 

+ * r2 = intermediate result     r10 = -1024 (for clipping)

+ * r3 = previous left sample    r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = current sample

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr = updated right sample

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_2:

+        ldr     r3, [r1, #-8]

+

+term_minus_2_loop:

+        ldr     ip, [r1, #4]            @ for right channel the decorrelation value

+        movs    r3, r3, asl #4          @  is the previous left sample (in r3)

+        mov     r11, #0x80000000

+        mov     lr, ip

+        smlalne r11, lr, r0, r3

+        strne   lr, [r1, #4]

+        cmpne   ip, #0

+        beq     .L380

+        teq     ip, r3                  @ update weight based on signs

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #(1024 << 18)               @ then clip weight to +/-1024

+        movgt   r0, #(1024 << 18)

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L380:  ldr     r2, [r1], #8            @ for left channel the decorrelation value

+        movs    lr, lr, asl #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r4, lr

+        strne   r3, [r1, #-8]

+        cmpne   r2, #0

+        beq     .L388

+        teq     r2, lr

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #(1024 << 18)

+        movgt   r4, #(1024 << 18)

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L388:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_2_loop

+

+        str     r3, [r5, #40]           @ else store left channel and exit

+        b       common_exit

+

+/*

+ ******************************************************************************

+ * Loop to handle term = -3 condition

+ *

+ * r0 = dpp->weight_B           r8 = previous left sample

+ * r1 = bptr                    r9 = 

+ * r2 = current left sample     r10 = -1024 (for clipping)

+ * r3 = previous right sample   r11 = lo accumulator (for rounding)

+ * r4 = dpp->weight_A           ip = intermediate result

+ * r5 = dpp                     sp =

+ * r6 = dpp->delta              lr =

+ * r7 = eptr                    pc =

+ *******************************************************************************

+ */

+

+term_minus_3:

+        ldr     r3, [r1, #-4]           @ load previous samples

+        ldr     r8, [r1, #-8]

+

+term_minus_3_loop:

+        ldr     ip, [r1], #4

+        movs    r3, r3, asl #4

+        mov     r11, #0x80000000

+        mov     r2, ip

+        smlalne r11, r2, r4, r3

+        strne   r2, [r1, #-4]

+        cmpne   ip, #0

+        beq     .L399

+        teq     ip, r3                  @ update weight based on signs

+        submi   r4, r4, r6

+        addpl   r4, r4, r6

+        cmp     r4, #(1024 << 18)       @ then clip weight to +/-1024

+        movgt   r4, #(1024 << 18)

+        cmp     r4, r10

+        movlt   r4, r10

+

+.L399:  movs    ip, r8, asl #4          @ ip = previous left we use now

+        mov     r8, r2                  @ r8 = current left we use next time

+        ldr     r2, [r1], #4

+        mov     r11, #0x80000000

+        mov     r3, r2

+        smlalne r11, r3, r0, ip

+        strne   r3, [r1, #-4]

+        cmpne   r2, #0

+        beq     .L407

+        teq     ip, r2

+        submi   r0, r0, r6

+        addpl   r0, r0, r6

+        cmp     r0, #(1024 << 18)

+        movgt   r0, #(1024 << 18)

+        cmp     r0, r10

+        movlt   r0, r10

+

+.L407:  cmp     r7, r1                  @ loop back if more samples to do

+        bhi     term_minus_3_loop

+

+        str     r3, [r5, #8]            @ else store previous samples & exit

+        str     r8, [r5, #40]

+

+/*

+ * Before finally exiting we must store weights back for next time

+ */

+

+common_exit:

+        mov     r0, r0, asr #18         @ restore weights to real magnitude

+        mov     r4, r4, asr #18

+        strh    r4, [r5, #4]

+        strh    r0, [r5, #6]

+        ldmfd   sp!, {r4 - r8, r10, r11, pc}

+

diff --git a/src/wavpack/bits.c b/src/wavpack/bits.c
new file mode 100644
index 00000000..dbfa0cae
--- /dev/null
+++ b/src/wavpack/bits.c
@@ -0,0 +1,140 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// bits.c

+

+// This module provides utilities to support the BitStream structure which is

+// used to read and write all WavPack audio data streams. It also contains a

+// wrapper for the stream I/O functions and a set of functions dealing with

+// endian-ness, both for enhancing portability. Finally, a debug wrapper for

+// the malloc() system is provided.

+

+#include "wavpack.h"

+

+#include <string.h>

+#include <ctype.h>

+

+////////////////////////// Bitstream functions ////////////////////////////////

+

+// Open the specified BitStream and associate with the specified buffer.

+

+static void bs_read (Bitstream *bs);

+

+void bs_open_read (Bitstream *bs, uchar *buffer_start, uchar *buffer_end, read_stream file, uint32_t file_bytes)

+{

+    CLEAR (*bs);

+    bs->buf = buffer_start;

+    bs->end = buffer_end;

+

+    if (file) {

+        bs->ptr = bs->end - 1;

+        bs->file_bytes = file_bytes;

+        bs->file = file;

+    }

+    else

+        bs->ptr = bs->buf - 1;

+

+    bs->wrap = bs_read;

+}

+

+// This function is only called from the getbit() and getbits() macros when

+// the BitStream has been exhausted and more data is required. Sinve these

+// bistreams no longer access files, this function simple sets an error and

+// resets the buffer.

+

+static void bs_read (Bitstream *bs)

+{

+    if (bs->file && bs->file_bytes) {

+        uint32_t bytes_read, bytes_to_read = bs->end - bs->buf;

+

+        if (bytes_to_read > bs->file_bytes)

+            bytes_to_read = bs->file_bytes;

+

+        bytes_read = bs->file (bs->buf, bytes_to_read);

+

+        if (bytes_read) {

+            bs->end = bs->buf + bytes_read;

+            bs->file_bytes -= bytes_read;

+        }

+        else {

+            memset (bs->buf, -1, bs->end - bs->buf);

+            bs->error = 1;

+        }

+    }

+    else

+        bs->error = 1;

+

+    if (bs->error)

+        memset (bs->buf, -1, bs->end - bs->buf);

+

+    bs->ptr = bs->buf;

+}

+

+/////////////////////// Endian Correction Routines ////////////////////////////

+

+void little_endian_to_native (void *data, char *format)

+{

+    uchar *cp = (uchar *) data;

+    int32_t temp;

+

+    while (*format) {

+        switch (*format) {

+            case 'L':

+                temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24);

+                * (int32_t *) cp = temp;

+                cp += 4;

+                break;

+

+            case 'S':

+                temp = cp [0] + (cp [1] << 8);

+                * (short *) cp = (short) temp;

+                cp += 2;

+                break;

+

+            default:

+                if (isdigit (*format))

+                    cp += *format - '0';

+

+                break;

+        }

+

+        format++;

+    }

+}

+

+void native_to_little_endian (void *data, char *format)

+{

+    uchar *cp = (uchar *) data;

+    int32_t temp;

+

+    while (*format) {

+        switch (*format) {

+            case 'L':

+                temp = * (int32_t *) cp;

+                *cp++ = (uchar) temp;

+                *cp++ = (uchar) (temp >> 8);

+                *cp++ = (uchar) (temp >> 16);

+                *cp++ = (uchar) (temp >> 24);

+                break;

+

+            case 'S':

+                temp = * (short *) cp;

+                *cp++ = (uchar) temp;

+                *cp++ = (uchar) (temp >> 8);

+                break;

+

+            default:

+                if (isdigit (*format))

+                    cp += *format - '0';

+

+                break;

+        }

+

+        format++;

+    }

+}

diff --git a/src/wavpack/coldfire.S b/src/wavpack/coldfire.S
new file mode 100644
index 00000000..93df9d82
--- /dev/null
+++ b/src/wavpack/coldfire.S
@@ -0,0 +1,525 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+/* This is an assembly optimized version of the following WavPack function:

+ *

+ * void decorr_stereo_pass_cont (struct decorr_pass *dpp,

+ *                               long *buffer, long sample_count);

+ *

+ * It performs a single pass of stereo decorrelation on the provided buffer.

+ * Note that this version of the function requires that the 8 previous stereo

+ * samples are visible and correct. In other words, it ignores the "samples_*"

+ * fields in the decorr_pass structure and gets the history data directly

+ * from the buffer. It does, however, return the appropriate history samples

+ * to the decorr_pass structure before returning.

+ *

+ * This is written to work on a MCF5249 processor, or any processor based on

+ * the ColdFire V2 core with an EMAC unit. The EMAC is perfectly suited for

+ * the "apply_weight" function of WavPack decorrelation because it provides

+ * the requires 40-bit product. The fractional rounding mode of the EMAC is not

+ * configurable and uses "round to even" while WavPack uses "round to larger",

+ * so the rounding has to be done manually.

+ */

+

+        .text

+        .align  2

+        .global decorr_stereo_pass_cont_mcf5249

+

+decorr_stereo_pass_cont_mcf5249:

+

+        lea     (-44, %sp), %sp

+        movem.l %d2-%d7/%a2-%a6, (%sp)

+        move.l  44+4(%sp), %a2          | a2 = dpp->

+        move.l  44+8(%sp), %a1          | a1 = bptr

+        move.w  2(%a2), %a3             | a3 = dpp->delta

+        move.w  4(%a2), %d3             | d3 = dpp->weight_A (sign extended)

+        ext.l   %d3

+        move.w  6(%a2), %d4             | d4 = dpp->weight_B (sign extended)

+        ext.l   %d4

+        move.l 44+12(%sp), %d0          | d0 = sample_count

+        jbeq    return_only             | if zero, nothing to do

+

+        lsl.l   #3, %d0                 | d5 = bptr + (sample_count * 8)

+        move.l  %d0, %d5

+        add.l   %a1, %d5

+

+        moveq.l #17, %d0                | left shift weights & delta 17 places

+        asl.l   %d0, %d3

+        asl.l   %d0, %d4

+        move.l  %a3, %d1

+        asl.l   %d0, %d1

+        move.l  %d1, %a3

+

+        moveq.l #0x20, %d6

+        move.l  %d6, %macsr             | set fractional mode for MAC

+        move.l  #0, %acc1               | acc1 = 0x00 0000 80 (for rounding)

+        move.l  #0x800000, %accext01

+        

+        move.l  #1024<<17, %d6          | d6 & d7 are weight clipping limits

+        move.l  #-1024<<17, %d7         | (only used by negative terms)

+

+        move.w  (%a2), %d0              | d0 = term

+        ext.l   %d0

+        cmp.l   #17, %d0

+        jbeq    term_17                 | term = 17

+        cmp.l   #18, %d0

+        jbeq    term_18                 | term = 18

+        addq.l  #1, %d0

+        jbeq    term_minus_1            | term = -1

+        addq.l  #1, %d0

+        jbeq    term_minus_2            | term = -2

+        addq.l  #1, %d0

+        jbeq    term_minus_3            | term = -3

+        jbra    term_default            | default term = 1 - 8

+

+|------------------------------------------------------------------------------

+| Loop to handle term = 17 condition

+|

+| a0 =                          d0 = (2 * bptr [-1]) - bptr [-2]

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_17:

+        move.l  -8(%a1), %d0            | d0 = 2 * bptr [-1] - bptr [-2]

+        add.l   %d0, %d0

+        sub.l   -16(%a1), %d0

+        beq     .L251                   | if zero, skip calculation

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_A

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L255

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L256                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        sub.l   %a3, %d3                | subtract again instead of branch

+.L256:  add.l   %a3, %d3                | add delta to weight

+

+.L255:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | update bptr [0] and store

+        move.l  %d2, (%a1)+

+

+.L253:  move.l  -8(%a1), %d0            | d0 = 2 * bptr [-1] - bptr [-2]

+        add.l   %d0, %d0

+        sub.l   -16(%a1), %d0

+        beq     .L257                   | if zero, skip calculations

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_B

+        mac.l   %d0, %d4, %acc0

+        move.l  (%a1), %d1

+        beq     .L254

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L259                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        sub.l   %a3, %d4                | subtract again instead of branch

+.L259:  add.l   %a3, %d4                | add delta to weight

+

+.L254:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | update bptr [0] and store

+        move.l  %d2, (%a1)+

+

+.L252:  cmp.l   %a1, %d5                | loop if bptr < eptr

+        jbhi    term_17

+        bra     term_17_18_finish       | exit through common path

+

+.L251:  addq.l  #4, %a1                 | update point and jump back into loop

+        bra     .L253

+

+.L257:  addq.l  #4, %a1                 | update point and jump back into loop

+        bra     .L252

+

+|------------------------------------------------------------------------------

+| Loop to handle term = 18 condition

+|

+| a0 =                          d0 = ((3 * bptr [-1]) - bptr [-2]) >> 1

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_18:

+        move.l  -8(%a1), %a0            | d0 = (3 * bptr [-1] - bptr [-2]) >> 1

+        lea     (%a0,%a0.l*2), %a0

+        move.l  %a0, %d0

+        sub.l   -16(%a1), %d0

+        asr.l   #1, %d0

+        beq     .L260

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_A

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L266

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L267                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        sub.l   %a3, %d3                | subtract again instead of branch

+.L267:  add.l   %a3, %d3                | add delta to weight

+

+.L266:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [0], store

+        move.l  %d2, (%a1)+

+

+.L268:  move.l  -8(%a1), %a0            | d0 = (3 * bptr [-1] - bptr [-2]) >> 1

+        lea     (%a0,%a0.l*2), %a0

+        move.l  %a0, %d0

+        sub.l   -16(%a1), %d0

+        asr.l   #1, %d0

+        beq     .L261

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_B

+        mac.l   %d0, %d4, %acc0

+        move.l  (%a1), %d1

+        beq     .L265

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L270                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        sub.l   %a3, %d4                | subtract again instead of branch

+.L270:  add.l   %a3, %d4                | add delta to weight

+

+.L265:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [0], store

+        move.l  %d2, (%a1)+

+

+.L269:  cmp.l   %a1, %d5                | loop if bptr < eptr

+        jbhi    term_18

+        bra     term_17_18_finish       | exit through common path

+

+.L260:  addq.l  #4, %a1                 | bump pointer and jump back into loop

+        bra     .L268

+

+.L261:  addq.l  #4, %a1                 | bump pointer and jump back into loop

+        bra     .L269

+

+term_17_18_finish:

+        move.l  -4(%a1), 40(%a2)        | restore dpp->samples_A [0-1], B [0-1]

+        move.l  -8(%a1), 8(%a2)

+        move.l  -12(%a1), 44(%a2)

+        move.l  -16(%a1), 12(%a2)

+        jbra    finish_up

+

+|------------------------------------------------------------------------------

+| Loop to handle default terms (i.e. 1 - 8)

+|

+| a0 = tptr                     d0 = tptr [0]

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_default:

+        move.w  (%a2), %d0              | a0 = a1 - (dpp->term * 8)

+        ext.l   %d0

+        lsl.l   #3, %d0

+        move.l  %a1, %a0

+        sub.l   %d0, %a0

+

+term_default_loop:

+        move.l  (%a0)+, %d0             | d0 = tptr [0], skip ahead if zero

+        beq     .L271

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_A

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L277

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L278                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        sub.l   %a3, %d3                | subtract again instead of branch

+.L278:  add.l   %a3, %d3                | add delta to weight

+

+.L277:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [0], store

+        move.l  %d2, (%a1)+

+

+.L275:  move.l  (%a0)+, %d0             | d0 = tptr [0], skip ahead if zero

+        beq     .L272

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + (d0 << 4) * weight_B

+        mac.l   %d0, %d4, %acc0

+        move.l  (%a1), %d1

+        beq     .L276

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L281                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        sub.l   %a3, %d4                | subtract again instead of branch

+.L281:  add.l   %a3, %d4                | add delta to weight

+

+.L276:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [0], store

+        move.l  %d2, (%a1)+

+

+.L274:  cmp.l   %a1, %d5                | loop back if bptr < eptr

+        jbhi    term_default_loop

+        move.w  (%a2), %d0              | d0 = term - 1

+        moveq.l #8, %d1                 | d1 = loop counter

+

+.L323:  subq.l  #1, %d0                 | back up & mask index

+        and.l   #7, %d0

+        move.l  -(%a1), 40(%a2,%d0.l*4) | store dpp->samples_B [d0]

+        move.l  -(%a1), 8(%a2,%d0.l*4)  | store dpp->samples_A [d0]

+        subq.l  #1, %d1                 | loop on count

+        jbne    .L323

+        jbra    finish_up

+

+.L271:  addq.l  #4, %a1                 | bump pointer and jump back into loop

+        bra     .L275

+

+.L272:  addq.l  #4, %a1                 | bump pointer and jump back into loop

+        bra     .L274

+

+

+|------------------------------------------------------------------------------

+| Loop to handle term = -1 condition

+|

+| a0 =                          d0 = decorrelation sample

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| a6 =                          d6 = 1024 << 17

+| a7 =                          d7 = -1024 << 17

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_minus_1:

+        move.l  -4(%a1), %d0            | d0 = bptr [-1]

+        beq     .L402

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_A)

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L405

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L404                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        cmp.l   %d7, %d3                | check for negative clip limit

+        bge     .L405

+        move.l  %d7, %d3

+        bra     .L405

+

+.L404:  add.l   %a3, %d3                | add delta to weight

+        cmp.l   %d6, %d3                | check for positive clip limit

+        ble     .L405

+        move.l  %d6, %d3

+

+.L405:  move.l  %acc0, %d0              | d2 = rounded product

+        add.l   %d1, %d0                | add applied weight to bptr [0], store

+        move.l  %d0, (%a1)+

+        beq     .L401

+

+.L410:  move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_B)

+        mac.l   %d0, %d4, %acc0

+        move.l  (%a1), %d1

+        beq     .L403

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L407                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        cmp.l   %d7, %d4                | check for negative clip limit

+        bge     .L403

+        move.l  %d7, %d4

+        bra     .L403

+

+.L407:  add.l   %a3, %d4                | add delta to weight

+        cmp.l   %d6, %d4                | check for positive clip limit

+        ble     .L403

+        move.l  %d6, %d4

+

+.L403:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [1], store

+        move.l  %d2, (%a1)+

+

+.L411:  cmp.l   %a1, %d5                | loop back if bptr < eptr

+        jbhi    term_minus_1

+        move.l  -4(%a1), 8(%a2)         | dpp->samples_A [0] = bptr [-1]

+        jbra    finish_up

+

+.L402:  move.l  (%a1)+, %d0

+        bne     .L410

+

+.L401:  addq.l  #4, %a1

+        bra     .L411

+

+

+|------------------------------------------------------------------------------

+| Loop to handle term = -2 condition

+|

+| a0 =                          d0 = decorrelation sample

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| a6 =                          d6 = 1024 << 17

+| a7 =                          d7 = -1024 << 17

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_minus_2:

+        move.l  -8(%a1), %d0            | d0 = bptr [-2]

+        beq     .L511

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_B)

+        mac.l   %d0, %d4, %acc0

+        move.l  4(%a1), %d1

+        beq     .L505

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L504                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        cmp.l   %d7, %d4                | ckeck for negative clip limit

+        bge     .L505

+        move.l  %d7, %d4

+        bra     .L505

+

+.L504:  add.l   %a3, %d4                | add delta to weight

+        cmp.l   %d6, %d4                | check for positive clip limit

+        ble     .L505

+        move.l  %d6, %d4

+

+.L505:  move.l  %acc0, %d0              | d2 = rounded product

+        add.l   %d1, %d0                | add applied weight to bptr [0], store

+        move.l  %d0, 4(%a1)

+        beq     .L512

+

+.L510:  move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_A)

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L503

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L507                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        cmp.l   %d7, %d3                | check for negative clip limit

+        bge     .L503

+        move.l  %d7, %d3

+        bra     .L503

+

+.L507:  add.l   %a3, %d3                | add delta to weight

+        cmp.l   %d6, %d3                | check for negative clip limit

+        ble     .L503

+        move.l  %d6, %d3

+

+.L503:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [1], store

+        move.l  %d2, (%a1)

+

+.L512:  addq.l  #8, %a1

+        cmp.l   %a1, %d5                | loop if bptr < eptr

+        jbhi    term_minus_2

+        move.l  -8(%a1), 40(%a2)        | dpp->samples_B [0] = bptr [-4]

+        jbra    finish_up

+

+.L511:  move.l  4(%a1), %d0

+        beq     .L512

+        bra     .L510

+

+

+|------------------------------------------------------------------------------

+| Loop to handle term = -3 condition

+|

+| a0 =                          d0 = decorrelation sample

+| a1 = bptr                     d1 = initial bptr [0]

+| a2 = dpp->                    d2 = updated bptr [0]

+| a3 = dpp->delta << 17         d3 = dpp->weight_A << 17

+| a4 =                          d4 = dpp->weight_B << 17

+| a5 =                          d5 = eptr

+| a6 =                          d6 = 1024 << 17

+| a7 =                          d7 = -1024 << 17

+| macsr = 0x20                  acc1 = 0x00 0000 80

+|------------------------------------------------------------------------------

+

+term_minus_3:

+        move.l  -4(%a1), %d0            | d0 = bptr [-1]

+        beq     .L301

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_A)

+        mac.l   %d0, %d3, %acc0

+        move.l  (%a1), %d1

+        beq     .L320

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L319                   | if same, add delta to weight

+        sub.l   %a3, %d3                | else subtract delta from weight

+        cmp.l   %d7, %d3                | check for negative clip limit

+        bge     .L320

+        move.l  %d7, %d3

+        bra     .L320

+

+.L319:  add.l   %a3, %d3                | add delta to weight

+        cmp.l   %d6, %d3                | check for positive clip limit

+        ble     .L320

+        move.l  %d6, %d3

+

+.L320:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [0], store

+        move.l  %d2, (%a1)+

+

+.L330:  move.l  -12(%a1), %d0           | d0 = bptr [-2]

+        beq     .L302

+        move.l  %acc1, %acc0

+        asl.l   #4, %d0                 | acc0 = acc1 + ((d0 << 4) * weight_B)

+        mac.l   %d0, %d4, %acc0

+        move.l  (%a1), %d1

+        beq     .L318

+        eor.l   %d1, %d0                | else compare signs

+        bge     .L322                   | if same, add delta to weight

+        sub.l   %a3, %d4                | else subtract delta from weight

+        cmp.l   %d7, %d4                | check for negative clip limit

+        bge     .L318

+        move.l  %d7, %d4

+        bra     .L318

+

+.L322:  add.l   %a3, %d4                | add delta to weight

+        cmp.l   %d6, %d4                | check for positive clip limit

+        ble     .L318

+        move.l  %d6, %d4

+

+.L318:  move.l  %acc0, %d2              | d2 = rounded product

+        add.l   %d1, %d2                | add applied weight to bptr [1], store

+        move.l  %d2, (%a1)+

+

+.L331:  cmp.l   %a1, %d5                | bptr, eptr

+        jbhi    term_minus_3

+        move.l  -4(%a1), 8(%a2)         | dpp->samples_A [0] = bptr [-1]

+        move.l  -8(%a1), 40(%a2)        | dpp->samples_B [0] = bptr [-2]

+        jbra    finish_up

+

+.L301:  addq.l  #4, %a1

+        bra     .L330

+

+.L302:  addq.l  #4, %a1

+        bra     .L331

+

+| finish and return

+

+finish_up:

+        moveq.l #17, %d0

+        asr.l   %d0, %d3

+        asr.l   %d0, %d4

+        move.w  %d3, 4(%a2)     | weight_A, dpp->weight_A

+        move.w  %d4, 6(%a2)     | weight_B, dpp->weight_B

+

+        clr.l   %d0             | clear up EMAC

+        move.l  %d0, %acc0

+        move.l  %d0, %acc1

+

+return_only:

+        movem.l (%sp), %d2-%d7/%a2-%a6

+        lea     (44,%sp), %sp

+        rts

diff --git a/src/wavpack/float.c b/src/wavpack/float.c
new file mode 100644
index 00000000..4b9b44ee
--- /dev/null
+++ b/src/wavpack/float.c
@@ -0,0 +1,50 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// float.c

+

+#include "wavpack.h"

+

+int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    int bytecnt = wpmd->byte_length;

+    char *byteptr = wpmd->data;

+

+    if (bytecnt != 4)

+        return FALSE;

+

+    wps->float_flags = *byteptr++;

+    wps->float_shift = *byteptr++;

+    wps->float_max_exp = *byteptr++;

+    wps->float_norm_exp = *byteptr;

+    return TRUE;

+}

+

+void float_values (WavpackStream *wps, int32_t *values, int32_t num_values)

+{

+    int shift = wps->float_max_exp - wps->float_norm_exp + wps->float_shift;

+

+    if (shift > 32)

+        shift = 32;

+    else if (shift < -32)

+        shift = -32;

+

+    while (num_values--) {

+        if (shift > 0)

+            *values <<= shift;

+        else if (shift < 0)

+            *values >>= -shift;

+

+        if (*values > 8388607L)

+            *values = 8388607L;

+        else if (*values < -8388608L)

+            *values = -8388608L;

+

+        values++;

+    }

+}

diff --git a/src/wavpack/license.txt b/src/wavpack/license.txt
new file mode 100644
index 00000000..98f6e6b1
--- /dev/null
+++ b/src/wavpack/license.txt
@@ -0,0 +1,25 @@
+               Copyright (c) 1998 - 2006 Conifer Software

+                          All rights reserved.

+

+Redistribution and use in source and binary forms, with or without

+modification, are permitted provided that the following conditions are met:

+

+    * Redistributions of source code must retain the above copyright notice,

+      this list of conditions and the following disclaimer.

+    * Redistributions in binary form must reproduce the above copyright notice,

+      this list of conditions and the following disclaimer in the

+      documentation and/or other materials provided with the distribution.

+    * Neither the name of Conifer Software nor the names of its contributors

+      may be used to endorse or promote products derived from this software

+      without specific prior written permission.

+

+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR

+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL

+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR

+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER

+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,

+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE

+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

diff --git a/src/wavpack/metadata.c b/src/wavpack/metadata.c
new file mode 100644
index 00000000..578b17f9
--- /dev/null
+++ b/src/wavpack/metadata.c
@@ -0,0 +1,105 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// metadata.c

+

+// This module handles the metadata structure introduced in WavPack 4.0

+

+#include "wavpack.h"

+

+int read_metadata_buff (WavpackContext *wpc, WavpackMetadata *wpmd)

+{

+    uchar tchar;

+

+    if (!wpc->infile (&wpmd->id, 1) || !wpc->infile (&tchar, 1))

+        return FALSE;

+

+    wpmd->byte_length = tchar << 1;

+

+    if (wpmd->id & ID_LARGE) {

+        wpmd->id &= ~ID_LARGE;

+

+        if (!wpc->infile (&tchar, 1))

+            return FALSE;

+

+        wpmd->byte_length += (int32_t) tchar << 9; 

+

+        if (!wpc->infile (&tchar, 1))

+            return FALSE;

+

+        wpmd->byte_length += (int32_t) tchar << 17;

+    }

+

+    if (wpmd->id & ID_ODD_SIZE) {

+        wpmd->id &= ~ID_ODD_SIZE;

+        wpmd->byte_length--;

+    }

+

+    if (wpmd->byte_length && wpmd->byte_length <= sizeof (wpc->read_buffer)) {

+        uint32_t bytes_to_read = wpmd->byte_length + (wpmd->byte_length & 1);

+

+        if (wpc->infile (wpc->read_buffer, bytes_to_read) != (int32_t) bytes_to_read) {

+            wpmd->data = NULL;

+            return FALSE;

+        }

+

+        wpmd->data = wpc->read_buffer;

+    }

+    else

+        wpmd->data = NULL;

+

+    return TRUE;

+}

+

+int process_metadata (WavpackContext *wpc, WavpackMetadata *wpmd)

+{

+    WavpackStream *wps = &wpc->stream;

+

+    switch (wpmd->id) {

+        case ID_DUMMY:

+            return TRUE;

+

+        case ID_DECORR_TERMS:

+            return read_decorr_terms (wps, wpmd);

+

+        case ID_DECORR_WEIGHTS:

+            return read_decorr_weights (wps, wpmd);

+

+        case ID_DECORR_SAMPLES:

+            return read_decorr_samples (wps, wpmd);

+

+        case ID_ENTROPY_VARS:

+            return read_entropy_vars (wps, wpmd);

+

+        case ID_HYBRID_PROFILE:

+            return read_hybrid_profile (wps, wpmd);

+

+        case ID_FLOAT_INFO:

+            return read_float_info (wps, wpmd);

+

+        case ID_INT32_INFO:

+            return read_int32_info (wps, wpmd);

+

+        case ID_CHANNEL_INFO:

+            return read_channel_info (wpc, wpmd);

+

+        case ID_CONFIG_BLOCK:

+            return read_config_info (wpc, wpmd);

+

+        case ID_WV_BITSTREAM:

+            return init_wv_bitstream (wpc, wpmd);

+

+        case ID_SHAPING_WEIGHTS:

+        case ID_WVC_BITSTREAM:

+        case ID_WVX_BITSTREAM:

+            return TRUE;

+

+        default:

+            return (wpmd->id & ID_OPTIONAL_DATA) ? TRUE : FALSE;

+    }

+}

diff --git a/src/wavpack/readme.txt b/src/wavpack/readme.txt
new file mode 100644
index 00000000..4ccbdf42
--- /dev/null
+++ b/src/wavpack/readme.txt
@@ -0,0 +1,68 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+This package contains a tiny version of the WavPack 4.40 decoder that might

+be used in a "resource limited" CPU environment or form the basis for a

+hardware decoding implementation. It is packaged with a demo command-line

+program that accepts a WavPack audio file on stdin and outputs a RIFF wav

+file to stdout. The program is standard C, and a win32 executable is

+included which was compiled under MS Visual C++ 6.0 using this command:

+

+cl /O1 /DWIN32 wvfilter.c wputils.c unpack.c float.c metadata.c words.c bits.c

+

+WavPack data is read with a stream reading callback. No direct seeking is

+provided for, but it is possible to start decoding anywhere in a WavPack

+stream. In this case, WavPack will be able to provide the sample-accurate

+position when it synchs with the data and begins decoding. The WIN32 macro

+is used for Windows to force the stdin and stdout streams to be binary mode.

+

+Compared to the previous version, this library has been optimized somewhat

+for improved performance in exchange for slightly larger code size. The

+library also now includes hand-optimized assembly language versions of the

+decorrelation functions for both the ColdFire (w/EMAC) and ARM processors.

+

+For demonstration purposes this uses a single static copy of the

+WavpackContext structure, so obviously it cannot be used for more than one

+file at a time. Also, this decoder will not handle "correction" files, plays

+only the first two channels of multi-channel files, and is limited in

+resolution in some large integer or floating point files (but always

+provides at least 24 bits of resolution). It also will not accept WavPack

+files from before version 4.0.

+

+The previous version of this library would handle float files by returning

+32-bit floating-point data (even though no floating point math was used).

+Because this library would normally be used for simply playing WavPack

+files where lossless performance (beyond 24-bits) is not relevant, I have

+changed this behavior. Now, these files will generate clipped 24-bit data.

+The MODE_FLOAT flag will still be returned by WavpackGetMode(), but the

+BitsPerSample and BytesPerSample queries will be 24 and 3, respectfully.

+What this means is that an application that can handle 24-bit data will

+now be able to handle floating point data (assuming that the MODE_FLOAT

+flag is ignored).

+

+To make this code viable on the greatest number of hardware platforms, the

+following are true:

+

+   speed is about 5x realtime on an AMD K6 300 MHz

+      ("high" mode 16/44 stereo; normal mode is about twice that fast)

+

+   no floating-point math required; just 32b * 32b = 32b int multiply

+

+   large data areas are static and less than 4K total

+   executable code and tables are less than 40K

+   no malloc / free usage

+

+To maintain compatibility on various platforms, the following conventions

+are used:

+

+   a "char" must be exactly 8-bits

+   a "short" must be exactly 16-bits

+   an "int" must be at least 16-bits, but may be larger

+   the "long" type is not used to avoid problems with 64-bit compilers

+

+Questions or comments should be directed to david@wavpack.com

diff --git a/src/wavpack/unpack.c b/src/wavpack/unpack.c
new file mode 100644
index 00000000..2bed5a0c
--- /dev/null
+++ b/src/wavpack/unpack.c
@@ -0,0 +1,785 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// unpack.c

+

+// This module actually handles the decompression of the audio data, except

+// for the entropy decoding which is handled by the words.c module. For

+// maximum efficiency, the conversion is isolated to tight loops that handle

+// an entire buffer.

+

+#include "wavpack.h"

+

+#include <stdlib.h>

+#include <string.h>

+

+#define LOSSY_MUTE

+

+///////////////////////////// executable code ////////////////////////////////

+

+// This function initializes everything required to unpack a WavPack block

+// and must be called before unpack_samples() is called to obtain audio data.

+// It is assumed that the WavpackHeader has been read into the wps->wphdr

+// (in the current WavpackStream). This is where all the metadata blocks are

+// scanned up to the one containing the audio bitstream.

+

+int unpack_init (WavpackContext *wpc)

+{

+    WavpackStream *wps = &wpc->stream;

+    WavpackMetadata wpmd;

+

+    if (wps->wphdr.block_samples && wps->wphdr.block_index != (uint32_t) -1)

+        wps->sample_index = wps->wphdr.block_index;

+

+    wps->mute_error = FALSE;

+    wps->crc = 0xffffffff;

+    CLEAR (wps->wvbits);

+    CLEAR (wps->decorr_passes);

+    CLEAR (wps->w);

+

+    while (read_metadata_buff (wpc, &wpmd)) {

+        if (!process_metadata (wpc, &wpmd)) {

+            strcpy (wpc->error_message, "invalid metadata!");

+            return FALSE;

+        }

+

+        if (wpmd.id == ID_WV_BITSTREAM)

+            break;

+    }

+

+    if (wps->wphdr.block_samples && !bs_is_open (&wps->wvbits)) {

+        strcpy (wpc->error_message, "invalid WavPack file!");

+        return FALSE;

+    }

+

+    if (wps->wphdr.block_samples) {

+        if ((wps->wphdr.flags & INT32_DATA) && wps->int32_sent_bits)

+            wpc->lossy_blocks = TRUE;

+

+        if ((wps->wphdr.flags & FLOAT_DATA) &&

+            wps->float_flags & (FLOAT_EXCEPTIONS | FLOAT_ZEROS_SENT | FLOAT_SHIFT_SENT | FLOAT_SHIFT_SAME))

+                wpc->lossy_blocks = TRUE;

+    }

+

+    return TRUE;

+}

+

+// This function initialzes the main bitstream for audio samples, which must

+// be in the "wv" file.

+

+int init_wv_bitstream (WavpackContext *wpc, WavpackMetadata *wpmd)

+{

+    WavpackStream *wps = &wpc->stream;

+

+    if (wpmd->data)

+        bs_open_read (&wps->wvbits, wpmd->data, (unsigned char *) wpmd->data + wpmd->byte_length, NULL, 0);

+    else if (wpmd->byte_length)

+        bs_open_read (&wps->wvbits, wpc->read_buffer, wpc->read_buffer + sizeof (wpc->read_buffer),

+            wpc->infile, wpmd->byte_length + (wpmd->byte_length & 1));

+

+    return TRUE;

+}

+

+// Read decorrelation terms from specified metadata block into the

+// decorr_passes array. The terms range from -3 to 8, plus 17 & 18;

+// other values are reserved and generate errors for now. The delta

+// ranges from 0 to 7 with all values valid. Note that the terms are

+// stored in the opposite order in the decorr_passes array compared

+// to packing.

+

+int read_decorr_terms (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    int termcnt = wpmd->byte_length;

+    uchar *byteptr = wpmd->data;

+    struct decorr_pass *dpp;

+

+    if (termcnt > MAX_NTERMS)

+        return FALSE;

+

+    wps->num_terms = termcnt;

+

+    for (dpp = wps->decorr_passes + termcnt - 1; termcnt--; dpp--) {

+        dpp->term = (int)(*byteptr & 0x1f) - 5;

+        dpp->delta = (*byteptr++ >> 5) & 0x7;

+

+        if (!dpp->term || dpp->term < -3 || (dpp->term > MAX_TERM && dpp->term < 17) || dpp->term > 18)

+            return FALSE;

+    }

+

+    return TRUE;

+}

+

+// Read decorrelation weights from specified metadata block into the

+// decorr_passes array. The weights range +/-1024, but are rounded and

+// truncated to fit in signed chars for metadata storage. Weights are

+// separate for the two channels and are specified from the "last" term

+// (first during encode). Unspecified weights are set to zero.

+

+int read_decorr_weights (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    int termcnt = wpmd->byte_length, tcount;

+    signed char *byteptr = wpmd->data;

+    struct decorr_pass *dpp;

+

+    if (!(wps->wphdr.flags & MONO_DATA))

+        termcnt /= 2;

+

+    if (termcnt > wps->num_terms)

+        return FALSE;

+

+    for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++)

+        dpp->weight_A = dpp->weight_B = 0;

+

+    while (--dpp >= wps->decorr_passes && termcnt--) {

+        dpp->weight_A = restore_weight (*byteptr++);

+

+        if (!(wps->wphdr.flags & MONO_DATA))

+            dpp->weight_B = restore_weight (*byteptr++);

+    }

+

+    return TRUE;

+}

+

+// Read decorrelation samples from specified metadata block into the

+// decorr_passes array. The samples are signed 32-bit values, but are

+// converted to signed log2 values for storage in metadata. Values are

+// stored for both channels and are specified from the "last" term

+// (first during encode) with unspecified samples set to zero. The

+// number of samples stored varies with the actual term value, so

+// those must obviously come first in the metadata.

+

+int read_decorr_samples (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    uchar *byteptr = wpmd->data;

+    uchar *endptr = byteptr + wpmd->byte_length;

+    struct decorr_pass *dpp;

+    int tcount;

+

+    for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) {

+        CLEAR (dpp->samples_A);

+        CLEAR (dpp->samples_B);

+    }

+

+    if (wps->wphdr.version == 0x402 && (wps->wphdr.flags & HYBRID_FLAG)) {

+        byteptr += 2;

+

+        if (!(wps->wphdr.flags & MONO_DATA))

+            byteptr += 2;

+    }

+

+    while (dpp-- > wps->decorr_passes && byteptr < endptr)

+        if (dpp->term > MAX_TERM) {

+            dpp->samples_A [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+            dpp->samples_A [1] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8)));

+            byteptr += 4;

+

+            if (!(wps->wphdr.flags & MONO_DATA)) {

+                dpp->samples_B [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+                dpp->samples_B [1] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8)));

+                byteptr += 4;

+            }

+        }

+        else if (dpp->term < 0) {

+            dpp->samples_A [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+            dpp->samples_B [0] = exp2s ((short)(byteptr [2] + (byteptr [3] << 8)));

+            byteptr += 4;

+        }

+        else {

+            int m = 0, cnt = dpp->term;

+

+            while (cnt--) {

+                dpp->samples_A [m] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+                byteptr += 2;

+

+                if (!(wps->wphdr.flags & MONO_DATA)) {

+                    dpp->samples_B [m] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+                    byteptr += 2;

+                }

+

+                m++;

+            }

+        }

+

+    return byteptr == endptr;

+}

+

+// Read the int32 data from the specified metadata into the specified stream.

+// This data is used for integer data that has more than 24 bits of magnitude

+// or, in some cases, used to eliminate redundant bits from any audio stream.

+

+int read_int32_info (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    int bytecnt = wpmd->byte_length;

+    char *byteptr = wpmd->data;

+

+    if (bytecnt != 4)

+        return FALSE;

+

+    wps->int32_sent_bits = *byteptr++;

+    wps->int32_zeros = *byteptr++;

+    wps->int32_ones = *byteptr++;

+    wps->int32_dups = *byteptr;

+    return TRUE;

+}

+

+// Read multichannel information from metadata. The first byte is the total

+// number of channels and the following bytes represent the channel_mask

+// as described for Microsoft WAVEFORMATEX.

+

+int read_channel_info (WavpackContext *wpc, WavpackMetadata *wpmd)

+{

+    int bytecnt = wpmd->byte_length, shift = 0;

+    char *byteptr = wpmd->data;

+    uint32_t mask = 0;

+

+    if (!bytecnt || bytecnt > 5)

+        return FALSE;

+

+    wpc->config.num_channels = *byteptr++;

+

+    while (--bytecnt) {

+        mask |= (uint32_t) *byteptr++ << shift;

+        shift += 8;

+    }

+

+    wpc->config.channel_mask = mask;

+    return TRUE;

+}

+

+// Read configuration information from metadata.

+

+int read_config_info (WavpackContext *wpc, WavpackMetadata *wpmd)

+{

+    int bytecnt = wpmd->byte_length;

+    uchar *byteptr = wpmd->data;

+

+    if (bytecnt >= 3) {

+        wpc->config.flags &= 0xff;

+        wpc->config.flags |= (int32_t) *byteptr++ << 8;

+        wpc->config.flags |= (int32_t) *byteptr++ << 16;

+        wpc->config.flags |= (int32_t) *byteptr << 24;

+    }

+

+    return TRUE;

+}

+

+// This monster actually unpacks the WavPack bitstream(s) into the specified

+// buffer as 32-bit integers or floats (depending on orignal data). Lossy

+// samples will be clipped to their original limits (i.e. 8-bit samples are

+// clipped to -128/+127) but are still returned in int32_ts. It is up to the

+// caller to potentially reformat this for the final output including any

+// multichannel distribution, block alignment or endian compensation. The

+// function unpack_init() must have been called and the entire WavPack block

+// must still be visible (although wps->blockbuff will not be accessed again).

+// For maximum clarity, the function is broken up into segments that handle

+// various modes. This makes for a few extra infrequent flag checks, but

+// makes the code easier to follow because the nesting does not become so

+// deep. For maximum efficiency, the conversion is isolated to tight loops

+// that handle an entire buffer. The function returns the total number of

+// samples unpacked, which can be less than the number requested if an error

+// occurs or the end of the block is reached.

+

+#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)

+extern void decorr_stereo_pass_cont_mcf5249 (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+#elif defined(CPU_ARM) && !defined(SIMULATOR)

+extern void decorr_stereo_pass_cont_arm (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+extern void decorr_stereo_pass_cont_arml (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+#else

+static void decorr_stereo_pass_cont (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+#endif

+

+static void decorr_mono_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count);

+static void fixup_samples (WavpackStream *wps, int32_t *buffer, uint32_t sample_count);

+

+int32_t unpack_samples (WavpackContext *wpc, int32_t *buffer, uint32_t sample_count)

+{

+    WavpackStream *wps = &wpc->stream;

+    uint32_t flags = wps->wphdr.flags, crc = wps->crc, i;

+    int32_t mute_limit = (1L << ((flags & MAG_MASK) >> MAG_LSB)) + 2;

+    struct decorr_pass *dpp;

+    int32_t *bptr, *eptr;

+    int tcount;

+

+    if (wps->sample_index + sample_count > wps->wphdr.block_index + wps->wphdr.block_samples)

+        sample_count = wps->wphdr.block_index + wps->wphdr.block_samples - wps->sample_index;

+

+    if (wps->mute_error) {

+        memset (buffer, 0, sample_count * (flags & MONO_FLAG ? 4 : 8));

+        wps->sample_index += sample_count;

+        return sample_count;

+    }

+

+    if (flags & HYBRID_FLAG)

+        mute_limit *= 2;

+

+    ///////////////////// handle version 4 mono data /////////////////////////

+

+    if (flags & MONO_DATA) {

+        eptr = buffer + sample_count;

+        i = get_words (buffer, sample_count, flags, &wps->w, &wps->wvbits);

+

+        for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++)

+            decorr_mono_pass (dpp, buffer, sample_count);

+

+        for (bptr = buffer; bptr < eptr; ++bptr) {

+            if (labs (bptr [0]) > mute_limit) {

+                i = bptr - buffer;

+                break;

+            }

+

+            crc = crc * 3 + bptr [0];

+        }

+    }

+

+    //////////////////// handle version 4 stereo data ////////////////////////

+

+    else {

+        eptr = buffer + (sample_count * 2);

+        i = get_words (buffer, sample_count, flags, &wps->w, &wps->wvbits);

+

+        if (sample_count < 16)

+            for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++)

+                decorr_stereo_pass (dpp, buffer, sample_count);

+        else

+            for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) {

+                decorr_stereo_pass (dpp, buffer, 8);

+#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)

+                decorr_stereo_pass_cont_mcf5249 (dpp, buffer + 16, sample_count - 8);

+#elif defined(CPU_ARM) && !defined(SIMULATOR)

+                if (((flags & MAG_MASK) >> MAG_LSB) > 15)

+                    decorr_stereo_pass_cont_arml (dpp, buffer + 16, sample_count - 8);

+                else

+                    decorr_stereo_pass_cont_arm (dpp, buffer + 16, sample_count - 8);

+#else

+                decorr_stereo_pass_cont (dpp, buffer + 16, sample_count - 8);

+#endif

+            }

+

+        if (flags & JOINT_STEREO)

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                bptr [0] += (bptr [1] -= (bptr [0] >> 1));

+

+                if (labs (bptr [0]) > mute_limit || labs (bptr [1]) > mute_limit) {

+                    i = (bptr - buffer) / 2;

+                    break;

+                }

+

+                crc = (crc * 3 + bptr [0]) * 3 + bptr [1];

+            }

+        else

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                if (labs (bptr [0]) > mute_limit || labs (bptr [1]) > mute_limit) {

+                    i = (bptr - buffer) / 2;

+                    break;

+                }

+

+                crc = (crc * 3 + bptr [0]) * 3 + bptr [1];

+            }

+    }

+

+    if (i != sample_count) {

+        memset (buffer, 0, sample_count * (flags & MONO_FLAG ? 4 : 8));

+        wps->mute_error = TRUE;

+        i = sample_count;

+    }

+

+    fixup_samples (wps, buffer, i);

+

+    if (flags & FALSE_STEREO) {

+        int32_t *dptr = buffer + i * 2;

+        int32_t *sptr = buffer + i;

+        int32_t c = i;

+

+        while (c--) {

+            *--dptr = *--sptr;

+            *--dptr = *sptr;

+        }

+    }

+

+    wps->sample_index += i;

+    wps->crc = crc;

+

+    return i;

+}

+

+static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count)

+{

+    int32_t delta = dpp->delta, weight_A = dpp->weight_A, weight_B = dpp->weight_B;

+    int32_t *bptr, *eptr = buffer + (sample_count * 2), sam_A, sam_B;

+    int m, k;

+

+    switch (dpp->term) {

+

+        case 17:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = 2 * dpp->samples_A [0] - dpp->samples_A [1];

+                dpp->samples_A [1] = dpp->samples_A [0];

+                dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [0];

+

+                sam_A = 2 * dpp->samples_B [0] - dpp->samples_B [1];

+                dpp->samples_B [1] = dpp->samples_B [0];

+                dpp->samples_B [0] = apply_weight (weight_B, sam_A) + bptr [1];

+                update_weight (weight_B, delta, sam_A, bptr [1]);

+                bptr [1] = dpp->samples_B [0];

+            }

+

+            break;

+

+        case 18:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1;

+                dpp->samples_A [1] = dpp->samples_A [0];

+                dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [0];

+

+                sam_A = (3 * dpp->samples_B [0] - dpp->samples_B [1]) >> 1;

+                dpp->samples_B [1] = dpp->samples_B [0];

+                dpp->samples_B [0] = apply_weight (weight_B, sam_A) + bptr [1];

+                update_weight (weight_B, delta, sam_A, bptr [1]);

+                bptr [1] = dpp->samples_B [0];

+            }

+

+            break;

+

+        default:

+            for (m = 0, k = dpp->term & (MAX_TERM - 1), bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = dpp->samples_A [m];

+                dpp->samples_A [k] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [k];

+

+                sam_A = dpp->samples_B [m];

+                dpp->samples_B [k] = apply_weight (weight_B, sam_A) + bptr [1];

+                update_weight (weight_B, delta, sam_A, bptr [1]);

+                bptr [1] = dpp->samples_B [k];

+

+                m = (m + 1) & (MAX_TERM - 1);

+                k = (k + 1) & (MAX_TERM - 1);

+            }

+

+            if (m) {

+                int32_t temp_samples [MAX_TERM];

+

+                memcpy (temp_samples, dpp->samples_A, sizeof (dpp->samples_A));

+

+                for (k = 0; k < MAX_TERM; k++, m++)

+                    dpp->samples_A [k] = temp_samples [m & (MAX_TERM - 1)];

+

+                memcpy (temp_samples, dpp->samples_B, sizeof (dpp->samples_B));

+

+                for (k = 0; k < MAX_TERM; k++, m++)

+                    dpp->samples_B [k] = temp_samples [m & (MAX_TERM - 1)];

+            }

+

+            break;

+

+        case -1:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = bptr [0] + apply_weight (weight_A, dpp->samples_A [0]);

+                update_weight_clip (weight_A, delta, dpp->samples_A [0], bptr [0]);

+                bptr [0] = sam_A;

+                dpp->samples_A [0] = bptr [1] + apply_weight (weight_B, sam_A);

+                update_weight_clip (weight_B, delta, sam_A, bptr [1]);

+                bptr [1] = dpp->samples_A [0];

+            }

+

+            break;

+

+        case -2:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_B = bptr [1] + apply_weight (weight_B, dpp->samples_B [0]);

+                update_weight_clip (weight_B, delta, dpp->samples_B [0], bptr [1]);

+                bptr [1] = sam_B;

+                dpp->samples_B [0] = bptr [0] + apply_weight (weight_A, sam_B);

+                update_weight_clip (weight_A, delta, sam_B, bptr [0]);

+                bptr [0] = dpp->samples_B [0];

+            }

+

+            break;

+

+        case -3:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = bptr [0] + apply_weight (weight_A, dpp->samples_A [0]);

+                update_weight_clip (weight_A, delta, dpp->samples_A [0], bptr [0]);

+                sam_B = bptr [1] + apply_weight (weight_B, dpp->samples_B [0]);

+                update_weight_clip (weight_B, delta, dpp->samples_B [0], bptr [1]);

+                bptr [0] = dpp->samples_B [0] = sam_A;

+                bptr [1] = dpp->samples_A [0] = sam_B;

+            }

+

+            break;

+    }

+

+    dpp->weight_A = weight_A;

+    dpp->weight_B = weight_B;

+}

+

+#if (!defined(CPU_COLDFIRE) && !defined(CPU_ARM)) || defined(SIMULATOR)

+

+static void decorr_stereo_pass_cont (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count)

+{

+    int32_t delta = dpp->delta, weight_A = dpp->weight_A, weight_B = dpp->weight_B;

+    int32_t *bptr, *tptr, *eptr = buffer + (sample_count * 2), sam_A, sam_B;

+    int k, i;

+

+    switch (dpp->term) {

+

+        case 17:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = 2 * bptr [-2] - bptr [-4];

+                bptr [0] = apply_weight (weight_A, sam_A) + (sam_B = bptr [0]);

+                update_weight (weight_A, delta, sam_A, sam_B);

+

+                sam_A = 2 * bptr [-1] - bptr [-3];

+                bptr [1] = apply_weight (weight_B, sam_A) + (sam_B = bptr [1]);

+                update_weight (weight_B, delta, sam_A, sam_B);

+            }

+

+            dpp->samples_B [0] = bptr [-1];

+            dpp->samples_A [0] = bptr [-2];

+            dpp->samples_B [1] = bptr [-3];

+            dpp->samples_A [1] = bptr [-4];

+            break;

+

+        case 18:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                sam_A = (3 * bptr [-2] - bptr [-4]) >> 1;

+                bptr [0] = apply_weight (weight_A, sam_A) + (sam_B = bptr [0]);

+                update_weight (weight_A, delta, sam_A, sam_B);

+

+                sam_A = (3 * bptr [-1] - bptr [-3]) >> 1;

+                bptr [1] = apply_weight (weight_B, sam_A) + (sam_B = bptr [1]);

+                update_weight (weight_B, delta, sam_A, sam_B);

+            }

+

+            dpp->samples_B [0] = bptr [-1];

+            dpp->samples_A [0] = bptr [-2];

+            dpp->samples_B [1] = bptr [-3];

+            dpp->samples_A [1] = bptr [-4];

+            break;

+

+        default:

+            for (bptr = buffer, tptr = buffer - (dpp->term * 2); bptr < eptr; bptr += 2, tptr += 2) {

+                bptr [0] = apply_weight (weight_A, tptr [0]) + (sam_A = bptr [0]);

+                update_weight (weight_A, delta, tptr [0], sam_A);

+

+                bptr [1] = apply_weight (weight_B, tptr [1]) + (sam_A = bptr [1]);

+                update_weight (weight_B, delta, tptr [1], sam_A);

+            }

+

+            for (k = dpp->term - 1, i = 8; i--; k--) {

+                dpp->samples_B [k & (MAX_TERM - 1)] = *--bptr;

+                dpp->samples_A [k & (MAX_TERM - 1)] = *--bptr;

+            }

+

+            break;

+

+        case -1:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                bptr [0] = apply_weight (weight_A, bptr [-1]) + (sam_A = bptr [0]);

+                update_weight_clip (weight_A, delta, bptr [-1], sam_A);

+                bptr [1] = apply_weight (weight_B, bptr [0]) + (sam_A = bptr [1]);

+                update_weight_clip (weight_B, delta, bptr [0], sam_A);

+            }

+

+            dpp->samples_A [0] = bptr [-1];

+            break;

+

+        case -2:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                bptr [1] = apply_weight (weight_B, bptr [-2]) + (sam_A = bptr [1]);

+                update_weight_clip (weight_B, delta, bptr [-2], sam_A);

+                bptr [0] = apply_weight (weight_A, bptr [1]) + (sam_A = bptr [0]);

+                update_weight_clip (weight_A, delta, bptr [1], sam_A);

+            }

+

+            dpp->samples_B [0] = bptr [-2];

+            break;

+

+        case -3:

+            for (bptr = buffer; bptr < eptr; bptr += 2) {

+                bptr [0] = apply_weight (weight_A, bptr [-1]) + (sam_A = bptr [0]);

+                update_weight_clip (weight_A, delta, bptr [-1], sam_A);

+                bptr [1] = apply_weight (weight_B, bptr [-2]) + (sam_A = bptr [1]);

+                update_weight_clip (weight_B, delta, bptr [-2], sam_A);

+            }

+

+            dpp->samples_A [0] = bptr [-1];

+            dpp->samples_B [0] = bptr [-2];

+            break;

+    }

+

+    dpp->weight_A = weight_A;

+    dpp->weight_B = weight_B;

+}

+

+#endif

+

+static void decorr_mono_pass (struct decorr_pass *dpp, int32_t *buffer, int32_t sample_count)

+{

+    int32_t delta = dpp->delta, weight_A = dpp->weight_A;

+    int32_t *bptr, *eptr = buffer + sample_count, sam_A;

+    int m, k;

+

+    switch (dpp->term) {

+

+        case 17:

+            for (bptr = buffer; bptr < eptr; bptr++) {

+                sam_A = 2 * dpp->samples_A [0] - dpp->samples_A [1];

+                dpp->samples_A [1] = dpp->samples_A [0];

+                dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [0];

+            }

+

+            break;

+

+        case 18:

+            for (bptr = buffer; bptr < eptr; bptr++) {

+                sam_A = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1;

+                dpp->samples_A [1] = dpp->samples_A [0];

+                dpp->samples_A [0] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [0];

+            }

+

+            break;

+

+        default:

+            for (m = 0, k = dpp->term & (MAX_TERM - 1), bptr = buffer; bptr < eptr; bptr++) {

+                sam_A = dpp->samples_A [m];

+                dpp->samples_A [k] = apply_weight (weight_A, sam_A) + bptr [0];

+                update_weight (weight_A, delta, sam_A, bptr [0]);

+                bptr [0] = dpp->samples_A [k];

+                m = (m + 1) & (MAX_TERM - 1);

+                k = (k + 1) & (MAX_TERM - 1);

+            }

+

+            if (m) {

+                int32_t temp_samples [MAX_TERM];

+

+                memcpy (temp_samples, dpp->samples_A, sizeof (dpp->samples_A));

+

+                for (k = 0; k < MAX_TERM; k++, m++)

+                    dpp->samples_A [k] = temp_samples [m & (MAX_TERM - 1)];

+            }

+

+            break;

+    }

+

+    dpp->weight_A = weight_A;

+}

+

+

+// This is a helper function for unpack_samples() that applies several final

+// operations. First, if the data is 32-bit float data, then that conversion

+// is done in the float.c module (whether lossy or lossless) and we return.

+// Otherwise, if the extended integer data applies, then that operation is

+// executed first. If the unpacked data is lossy (and not corrected) then

+// it is clipped and shifted in a single operation. Otherwise, if it's

+// lossless then the last step is to apply the final shift (if any).

+

+static void fixup_samples (WavpackStream *wps, int32_t *buffer, uint32_t sample_count)

+{

+    uint32_t flags = wps->wphdr.flags;

+    int shift = (flags & SHIFT_MASK) >> SHIFT_LSB;

+

+    if (flags & FLOAT_DATA) {

+        float_values (wps, buffer, (flags & MONO_FLAG) ? sample_count : sample_count * 2);

+        return;

+    }

+

+    if (flags & INT32_DATA) {

+        uint32_t count = (flags & MONO_FLAG) ? sample_count : sample_count * 2;

+        int sent_bits = wps->int32_sent_bits, zeros = wps->int32_zeros;

+        int ones = wps->int32_ones, dups = wps->int32_dups;

+        int32_t *dptr = buffer;

+

+        if (!(flags & HYBRID_FLAG) && !sent_bits && (zeros + ones + dups))

+            while (count--) {

+                if (zeros)

+                    *dptr <<= zeros;

+                else if (ones)

+                    *dptr = ((*dptr + 1) << ones) - 1;

+                else if (dups)

+                    *dptr = ((*dptr + (*dptr & 1)) << dups) - (*dptr & 1);

+

+                dptr++;

+            }

+        else

+            shift += zeros + sent_bits + ones + dups;

+    }

+

+    if (flags & HYBRID_FLAG) {

+        int32_t min_value, max_value, min_shifted, max_shifted;

+

+        switch (flags & BYTES_STORED) {

+            case 0:

+                min_shifted = (min_value = -128 >> shift) << shift;

+                max_shifted = (max_value = 127 >> shift) << shift;

+                break;

+

+            case 1:

+                min_shifted = (min_value = -32768 >> shift) << shift;

+                max_shifted = (max_value = 32767 >> shift) << shift;

+                break;

+

+            case 2:

+                min_shifted = (min_value = -8388608 >> shift) << shift;

+                max_shifted = (max_value = 8388607 >> shift) << shift;

+                break;

+

+            case 3:

+            default:

+                min_shifted = (min_value = (int32_t) 0x80000000 >> shift) << shift;

+                max_shifted = (max_value = (int32_t) 0x7FFFFFFF >> shift) << shift;

+                break;

+        }

+

+        if (!(flags & MONO_FLAG))

+            sample_count *= 2;

+

+        while (sample_count--) {

+            if (*buffer < min_value)

+                *buffer++ = min_shifted;

+            else if (*buffer > max_value)

+                *buffer++ = max_shifted;

+            else

+                *buffer++ <<= shift;

+        }

+    }

+    else if (shift) {

+        if (!(flags & MONO_FLAG))

+            sample_count *= 2;

+

+        while (sample_count--)

+            *buffer++ <<= shift;

+    }

+}

+

+// This function checks the crc value(s) for an unpacked block, returning the

+// number of actual crc errors detected for the block. The block must be

+// completely unpacked before this test is valid. For losslessly unpacked

+// blocks of float or extended integer data the extended crc is also checked.

+// Note that WavPack's crc is not a CCITT approved polynomial algorithm, but

+// is a much simpler method that is virtually as robust for real world data.

+

+int check_crc_error (WavpackContext *wpc)

+{

+    WavpackStream *wps = &wpc->stream;

+    int result = 0;

+

+    if (wps->crc != wps->wphdr.crc)

+        ++result;

+

+    return result;

+}

diff --git a/src/wavpack/wavpack.h b/src/wavpack/wavpack.h
new file mode 100644
index 00000000..7c260586
--- /dev/null
+++ b/src/wavpack/wavpack.h
@@ -0,0 +1,384 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2004 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// wavpack.h

+

+#include <sys/types.h>

+

+// This header file contains all the definitions required by WavPack.

+

+#ifdef __BORLANDC__

+typedef unsigned long uint32_t;

+typedef long int32_t;

+#elif defined(_WIN32) && !defined(__MINGW32__)

+#include <stdlib.h>

+typedef unsigned __int64 uint64_t;

+typedef unsigned __int32 uint32_t;

+typedef __int64 int64_t;

+typedef __int32 int32_t;

+#else

+#include <inttypes.h>

+#endif

+

+typedef unsigned char   uchar;

+

+#if !defined(__GNUC__) || defined(WIN32)

+typedef unsigned short  ushort;

+typedef unsigned int    uint;

+#endif

+

+#include <stdio.h>

+

+#define FALSE 0

+#define TRUE 1

+

+////////////////////////////// WavPack Header /////////////////////////////////

+

+// Note that this is the ONLY structure that is written to (or read from)

+// WavPack 4.0 files, and is the preamble to every block in both the .wv

+// and .wvc files.

+

+typedef struct {

+    char ckID [4];

+    uint32_t ckSize;

+    short version;

+    uchar track_no, index_no;

+    uint32_t total_samples, block_index, block_samples, flags, crc;

+} WavpackHeader;

+

+#define WavpackHeaderFormat "4LS2LLLLL"

+

+// or-values for "flags"

+

+#define BYTES_STORED    3       // 1-4 bytes/sample

+#define MONO_FLAG       4       // not stereo

+#define HYBRID_FLAG     8       // hybrid mode

+#define JOINT_STEREO    0x10    // joint stereo

+#define CROSS_DECORR    0x20    // no-delay cross decorrelation

+#define HYBRID_SHAPE    0x40    // noise shape (hybrid mode only)

+#define FLOAT_DATA      0x80    // ieee 32-bit floating point data

+

+#define INT32_DATA      0x100   // special extended int handling

+#define HYBRID_BITRATE  0x200   // bitrate noise (hybrid mode only)

+#define HYBRID_BALANCE  0x400   // balance noise (hybrid stereo mode only)

+

+#define INITIAL_BLOCK   0x800   // initial block of multichannel segment

+#define FINAL_BLOCK     0x1000  // final block of multichannel segment

+

+#define SHIFT_LSB       13

+#define SHIFT_MASK      (0x1fL << SHIFT_LSB)

+

+#define MAG_LSB         18

+#define MAG_MASK        (0x1fL << MAG_LSB)

+

+#define SRATE_LSB       23

+#define SRATE_MASK      (0xfL << SRATE_LSB)

+

+#define FALSE_STEREO    0x40000000      // block is stereo, but data is mono

+

+#define IGNORED_FLAGS   0x18000000      // reserved, but ignore if encountered

+#define NEW_SHAPING     0x20000000      // use IIR filter for negative shaping

+#define UNKNOWN_FLAGS   0x80000000      // also reserved, but refuse decode if

+                                        //  encountered

+

+#define MONO_DATA (MONO_FLAG | FALSE_STEREO)

+

+#define MIN_STREAM_VERS     0x402       // lowest stream version we'll decode

+#define MAX_STREAM_VERS     0x410       // highest stream version we'll decode

+

+//////////////////////////// WavPack Metadata /////////////////////////////////

+

+// This is an internal representation of metadata.

+

+typedef struct {

+    int32_t byte_length;

+    void *data;

+    uchar id;

+} WavpackMetadata;

+

+#define ID_OPTIONAL_DATA        0x20

+#define ID_ODD_SIZE             0x40

+#define ID_LARGE                0x80

+

+#define ID_DUMMY                0x0

+#define ID_ENCODER_INFO         0x1

+#define ID_DECORR_TERMS         0x2

+#define ID_DECORR_WEIGHTS       0x3

+#define ID_DECORR_SAMPLES       0x4

+#define ID_ENTROPY_VARS         0x5

+#define ID_HYBRID_PROFILE       0x6

+#define ID_SHAPING_WEIGHTS      0x7

+#define ID_FLOAT_INFO           0x8

+#define ID_INT32_INFO           0x9

+#define ID_WV_BITSTREAM         0xa

+#define ID_WVC_BITSTREAM        0xb

+#define ID_WVX_BITSTREAM        0xc

+#define ID_CHANNEL_INFO         0xd

+

+#define ID_RIFF_HEADER          (ID_OPTIONAL_DATA | 0x1)

+#define ID_RIFF_TRAILER         (ID_OPTIONAL_DATA | 0x2)

+#define ID_REPLAY_GAIN          (ID_OPTIONAL_DATA | 0x3)

+#define ID_CUESHEET             (ID_OPTIONAL_DATA | 0x4)

+#define ID_CONFIG_BLOCK         (ID_OPTIONAL_DATA | 0x5)

+#define ID_MD5_CHECKSUM         (ID_OPTIONAL_DATA | 0x6)

+

+///////////////////////// WavPack Configuration ///////////////////////////////

+

+// This internal structure is used during encode to provide configuration to

+// the encoding engine and during decoding to provide fle information back to

+// the higher level functions. Not all fields are used in both modes.

+

+typedef struct {

+    int bits_per_sample, bytes_per_sample;

+    int num_channels, float_norm_exp;

+    uint32_t flags, sample_rate, channel_mask;

+} WavpackConfig;

+

+#define CONFIG_BYTES_STORED     3       // 1-4 bytes/sample

+#define CONFIG_MONO_FLAG        4       // not stereo

+#define CONFIG_HYBRID_FLAG      8       // hybrid mode

+#define CONFIG_JOINT_STEREO     0x10    // joint stereo

+#define CONFIG_CROSS_DECORR     0x20    // no-delay cross decorrelation

+#define CONFIG_HYBRID_SHAPE     0x40    // noise shape (hybrid mode only)

+#define CONFIG_FLOAT_DATA       0x80    // ieee 32-bit floating point data

+

+#define CONFIG_FAST_FLAG        0x200   // fast mode

+#define CONFIG_HIGH_FLAG        0x800   // high quality mode

+#define CONFIG_VERY_HIGH_FLAG   0x1000  // very high

+#define CONFIG_BITRATE_KBPS     0x2000  // bitrate is kbps, not bits / sample

+#define CONFIG_AUTO_SHAPING     0x4000  // automatic noise shaping

+#define CONFIG_SHAPE_OVERRIDE   0x8000  // shaping mode specified

+#define CONFIG_JOINT_OVERRIDE   0x10000 // joint-stereo mode specified

+#define CONFIG_CREATE_EXE       0x40000 // create executable

+#define CONFIG_CREATE_WVC       0x80000 // create correction file

+#define CONFIG_OPTIMIZE_WVC     0x100000 // maximize bybrid compression

+#define CONFIG_CALC_NOISE       0x800000 // calc noise in hybrid mode

+#define CONFIG_LOSSY_MODE       0x1000000 // obsolete (for information)

+#define CONFIG_EXTRA_MODE       0x2000000 // extra processing mode

+#define CONFIG_SKIP_WVX         0x4000000 // no wvx stream w/ floats & big ints

+#define CONFIG_MD5_CHECKSUM     0x8000000 // compute & store MD5 signature

+#define CONFIG_OPTIMIZE_MONO    0x80000000 // optimize for mono streams posing as stereo

+

+//////////////////////////////// WavPack Stream ///////////////////////////////

+

+// This internal structure contains everything required to handle a WavPack

+// "stream", which is defined as a stereo or mono stream of audio samples. For

+// multichannel audio several of these would be required. Each stream contains

+// pointers to hold a complete allocated block of WavPack data, although it's

+// possible to decode WavPack blocks without buffering an entire block.

+

+typedef int32_t (*read_stream)(void *, int32_t);

+

+typedef struct bs {

+    uchar *buf, *end, *ptr;

+    void (*wrap)(struct bs *bs);

+    uint32_t file_bytes, sr;

+    int error, bc;

+    read_stream file;

+} Bitstream;

+

+#define MAX_NTERMS 16

+#define MAX_TERM 8

+

+struct decorr_pass {

+    short term, delta, weight_A, weight_B;

+    int32_t samples_A [MAX_TERM], samples_B [MAX_TERM];

+};

+

+struct entropy_data {

+    uint32_t median [3], slow_level, error_limit;

+};

+

+struct words_data {

+    uint32_t bitrate_delta [2], bitrate_acc [2];

+    uint32_t pend_data, holding_one, zeros_acc;

+    int holding_zero, pend_count;

+    struct entropy_data c [2];

+};

+

+typedef struct {

+    WavpackHeader wphdr;

+    Bitstream wvbits;

+

+    struct words_data w;

+

+    int num_terms, mute_error;

+    uint32_t sample_index, crc;

+

+    uchar int32_sent_bits, int32_zeros, int32_ones, int32_dups;

+    uchar float_flags, float_shift, float_max_exp, float_norm_exp;

+ 

+    struct decorr_pass decorr_passes [MAX_NTERMS];

+

+} WavpackStream;

+

+// flags for float_flags:

+

+#define FLOAT_SHIFT_ONES 1      // bits left-shifted into float = '1'

+#define FLOAT_SHIFT_SAME 2      // bits left-shifted into float are the same

+#define FLOAT_SHIFT_SENT 4      // bits shifted into float are sent literally

+#define FLOAT_ZEROS_SENT 8      // "zeros" are not all real zeros

+#define FLOAT_NEG_ZEROS  0x10   // contains negative zeros

+#define FLOAT_EXCEPTIONS 0x20   // contains exceptions (inf, nan, etc.)

+

+/////////////////////////////// WavPack Context ///////////////////////////////

+

+// This internal structure holds everything required to encode or decode WavPack

+// files. It is recommended that direct access to this structure be minimized

+// and the provided utilities used instead.

+

+typedef struct {

+    WavpackConfig config;

+    WavpackStream stream;

+

+    uchar read_buffer [1024];

+    char error_message [80];

+

+    read_stream infile;

+    uint32_t total_samples, crc_errors, first_flags;

+    int open_flags, norm_offset, reduced_channels, lossy_blocks;

+

+} WavpackContext;

+

+//////////////////////// function prototypes and macros //////////////////////

+

+#define CLEAR(destin) memset (&destin, 0, sizeof (destin));

+

+// bits.c

+

+void bs_open_read (Bitstream *bs, uchar *buffer_start, uchar *buffer_end, read_stream file, uint32_t file_bytes);

+

+#define bs_is_open(bs) ((bs)->ptr != NULL)

+

+#define getbit(bs) ( \

+    (((bs)->bc) ? \

+        ((bs)->bc--, (bs)->sr & 1) : \

+            (((++((bs)->ptr) != (bs)->end) ? (void) 0 : (bs)->wrap (bs)), (bs)->bc = 7, ((bs)->sr = *((bs)->ptr)) & 1) \

+    ) ? \

+        ((bs)->sr >>= 1, 1) : \

+        ((bs)->sr >>= 1, 0) \

+)

+

+#define getbits(value, nbits, bs) { \

+    while ((nbits) > (bs)->bc) { \

+        if (++((bs)->ptr) == (bs)->end) (bs)->wrap (bs); \

+        (bs)->sr |= (int32_t)*((bs)->ptr) << (bs)->bc; \

+        (bs)->bc += 8; \

+    } \

+    *(value) = (bs)->sr; \

+    if ((bs)->bc > 32) { \

+        (bs)->bc -= (nbits); \

+        (bs)->sr = *((bs)->ptr) >> (8 - (bs)->bc); \

+    } \

+    else { \

+        (bs)->bc -= (nbits); \

+        (bs)->sr >>= (nbits); \

+    } \

+}

+

+void little_endian_to_native (void *data, char *format);

+void native_to_little_endian (void *data, char *format);

+

+// These macros implement the weight application and update operations

+// that are at the heart of the decorrelation loops. Note that when there

+// are several alternative versions of the same macro (marked with PERFCOND)

+// then the versions are functionally equivalent with respect to WavPack

+// decoding and the user should choose the one that provides the best

+// performance. This may be easier to check when NOT using the assembly

+// language optimizations.

+

+#if 1   // PERFCOND

+#define apply_weight_i(weight, sample) ((weight * sample + 512) >> 10)

+#else

+#define apply_weight_i(weight, sample) ((((weight * sample) >> 8) + 2) >> 2)

+#endif

+

+#define apply_weight_f(weight, sample) (((((sample & 0xffffL) * weight) >> 9) + \

+    (((sample & ~0xffffL) >> 9) * weight) + 1) >> 1)

+

+#if 1   // PERFCOND

+#define apply_weight(weight, sample) (sample != (short) sample ? \

+    apply_weight_f (weight, sample) : apply_weight_i (weight, sample))

+#else

+#define apply_weight(weight, sample) ((int32_t)((weight * (int64_t) sample + 512) >> 10))

+#endif

+

+#if 0   // PERFCOND

+#define update_weight(weight, delta, source, result) \

+    if (source && result) { int32_t s = (int32_t) (source ^ result) >> 31; weight = (delta ^ s) + (weight - s); }

+#elif 1

+#define update_weight(weight, delta, source, result) \

+    if (source && result) weight += (((source ^ result) >> 30) | 1) * delta

+#else

+#define update_weight(weight, delta, source, result) \

+    if (source && result) (source ^ result) < 0 ? (weight -= delta) : (weight += delta)

+#endif

+

+#define update_weight_clip(weight, delta, source, result) \

+    if (source && result && ((source ^ result) < 0 ? (weight -= delta) < -1024 : (weight += delta) > 1024)) \

+        weight = weight < 0 ? -1024 : 1024

+

+// unpack.c

+

+int unpack_init (WavpackContext *wpc);

+int init_wv_bitstream (WavpackContext *wpc, WavpackMetadata *wpmd);

+int read_decorr_terms (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_decorr_weights (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_decorr_samples (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_int32_info (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_channel_info (WavpackContext *wpc, WavpackMetadata *wpmd);

+int read_config_info (WavpackContext *wpc, WavpackMetadata *wpmd);

+int32_t unpack_samples (WavpackContext *wpc, int32_t *buffer, uint32_t sample_count);

+int check_crc_error (WavpackContext *wpc);

+

+// metadata.c stuff

+

+int read_metadata_buff (WavpackContext *wpc, WavpackMetadata *wpmd);

+int process_metadata (WavpackContext *wpc, WavpackMetadata *wpmd);

+

+// words.c stuff

+

+int read_entropy_vars (WavpackStream *wps, WavpackMetadata *wpmd);

+int read_hybrid_profile (WavpackStream *wps, WavpackMetadata *wpmd);

+int32_t get_words (int32_t *buffer, int nsamples, uint32_t flags,

+                struct words_data *w, Bitstream *bs);

+int32_t exp2s (int log);

+int restore_weight (signed char weight);

+

+#define WORD_EOF (1L << 31)

+

+// float.c

+

+int read_float_info (WavpackStream *wps, WavpackMetadata *wpmd);

+void float_values (WavpackStream *wps, int32_t *values, int32_t num_values);

+

+// wputils.c

+

+WavpackContext *WavpackOpenFileInput (read_stream infile, char *error);

+

+int WavpackGetMode (WavpackContext *wpc);

+

+#define MODE_WVC        0x1

+#define MODE_LOSSLESS   0x2

+#define MODE_HYBRID     0x4

+#define MODE_FLOAT      0x8

+#define MODE_VALID_TAG  0x10

+#define MODE_HIGH       0x20

+#define MODE_FAST       0x40

+

+uint32_t WavpackUnpackSamples (WavpackContext *wpc, int32_t *buffer, uint32_t samples);

+uint32_t WavpackGetNumSamples (WavpackContext *wpc);

+uint32_t WavpackGetSampleIndex (WavpackContext *wpc);

+int WavpackGetNumErrors (WavpackContext *wpc);

+int WavpackLossyBlocks (WavpackContext *wpc);

+uint32_t WavpackGetSampleRate (WavpackContext *wpc);

+int WavpackGetBitsPerSample (WavpackContext *wpc);

+int WavpackGetBytesPerSample (WavpackContext *wpc);

+int WavpackGetNumChannels (WavpackContext *wpc);

+int WavpackGetReducedChannels (WavpackContext *wpc);

diff --git a/src/wavpack/words.c b/src/wavpack/words.c
new file mode 100644
index 00000000..0e5a3db7
--- /dev/null
+++ b/src/wavpack/words.c
@@ -0,0 +1,560 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// words.c

+

+// This module provides entropy word encoding and decoding functions using

+// a variation on the Rice method.  This was introduced in version 3.93

+// because it allows splitting the data into a "lossy" stream and a

+// "correction" stream in a very efficient manner and is therefore ideal

+// for the "hybrid" mode.  For 4.0, the efficiency of this method was

+// significantly improved by moving away from the normal Rice restriction of

+// using powers of two for the modulus divisions and now the method can be

+// used for both hybrid and pure lossless encoding.

+

+// Samples are divided by median probabilities at 5/7 (71.43%), 10/49 (20.41%),

+// and 20/343 (5.83%). Each zone has 3.5 times fewer samples than the

+// previous. Using standard Rice coding on this data would result in 1.4

+// bits per sample average (not counting sign bit). However, there is a

+// very simple encoding that is over 99% efficient with this data and

+// results in about 1.22 bits per sample.

+

+#include "wavpack.h"

+

+#include <string.h>

+

+//////////////////////////////// local macros /////////////////////////////////

+

+#define LIMIT_ONES 16   // maximum consecutive 1s sent for "div" data

+

+// these control the time constant "slow_level" which is used for hybrid mode

+// that controls bitrate as a function of residual level (HYBRID_BITRATE).

+#define SLS 8

+#define SLO ((1 << (SLS - 1)))

+

+// these control the time constant of the 3 median level breakpoints

+#define DIV0 128        // 5/7 of samples

+#define DIV1 64         // 10/49 of samples

+#define DIV2 32         // 20/343 of samples

+

+// this macro retrieves the specified median breakpoint (without frac; min = 1)

+#define GET_MED(med) (((c->median [med]) >> 4) + 1)

+

+// These macros update the specified median breakpoints. Note that the median

+// is incremented when the sample is higher than the median, else decremented.

+// They are designed so that the median will never drop below 1 and the value

+// is essentially stationary if there are 2 increments for every 5 decrements.

+

+#define INC_MED0() (c->median [0] += ((c->median [0] + DIV0) / DIV0) * 5)

+#define DEC_MED0() (c->median [0] -= ((c->median [0] + (DIV0-2)) / DIV0) * 2)

+#define INC_MED1() (c->median [1] += ((c->median [1] + DIV1) / DIV1) * 5)

+#define DEC_MED1() (c->median [1] -= ((c->median [1] + (DIV1-2)) / DIV1) * 2)

+#define INC_MED2() (c->median [2] += ((c->median [2] + DIV2) / DIV2) * 5)

+#define DEC_MED2() (c->median [2] -= ((c->median [2] + (DIV2-2)) / DIV2) * 2)

+

+#define count_bits(av) ( \

+ (av) < (1 << 8) ? nbits_table [av] : \

+  ( \

+   (av) < (1L << 16) ? nbits_table [(av) >> 8] + 8 : \

+   ((av) < (1L << 24) ? nbits_table [(av) >> 16] + 16 : nbits_table [(av) >> 24] + 24) \

+  ) \

+)

+

+///////////////////////////// local table storage ////////////////////////////

+

+const char nbits_table [] = {

+    0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,     // 0 - 15

+    5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,     // 16 - 31

+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     // 32 - 47

+    6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,     // 48 - 63

+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     // 64 - 79

+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     // 80 - 95

+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     // 96 - 111

+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,     // 112 - 127

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 128 - 143

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 144 - 159

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 160 - 175

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 176 - 191

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 192 - 207

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 208 - 223

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,     // 224 - 239

+    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8      // 240 - 255

+};

+

+static const uchar log2_table [] = {

+    0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15,

+    0x16, 0x18, 0x19, 0x1a, 0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a,

+    0x2c, 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x38, 0x39, 0x3b, 0x3c, 0x3d, 0x3e,

+    0x3f, 0x41, 0x42, 0x43, 0x44, 0x45, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0x51,

+    0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63,

+    0x64, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x74, 0x75,

+    0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85,

+    0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,

+    0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,

+    0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb2,

+    0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc0,

+    0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xce,

+    0xcf, 0xd0, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd8, 0xd9, 0xda, 0xdb,

+    0xdc, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe4, 0xe5, 0xe6, 0xe7, 0xe7,

+    0xe8, 0xe9, 0xea, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xee, 0xef, 0xf0, 0xf1, 0xf1, 0xf2, 0xf3, 0xf4,

+    0xf4, 0xf5, 0xf6, 0xf7, 0xf7, 0xf8, 0xf9, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, 0xfe, 0xff, 0xff

+};

+

+static const uchar exp2_table [] = {

+    0x00, 0x01, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b,

+    0x0b, 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x12, 0x13, 0x13, 0x14, 0x15, 0x16, 0x16,

+    0x17, 0x18, 0x19, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23,

+    0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,

+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3d,

+    0x3e, 0x3f, 0x40, 0x41, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x48, 0x49, 0x4a, 0x4b,

+    0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,

+    0x5b, 0x5c, 0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,

+    0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,

+    0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x87, 0x88, 0x89, 0x8a,

+    0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,

+    0x9c, 0x9d, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad,

+    0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0,

+    0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc8, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xd0, 0xd2, 0xd3, 0xd4,

+    0xd6, 0xd7, 0xd8, 0xd9, 0xdb, 0xdc, 0xdd, 0xde, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe8, 0xe9,

+    0xea, 0xec, 0xed, 0xee, 0xf0, 0xf1, 0xf2, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfc, 0xfd, 0xff

+};

+

+static const char ones_count_table [] = {

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,

+    0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,8

+};

+

+///////////////////////////// executable code ////////////////////////////////

+

+void init_words (WavpackStream *wps)

+{

+    CLEAR (wps->w);

+}

+

+static int mylog2 (uint32_t avalue);

+

+// Read the median log2 values from the specifed metadata structure, convert

+// them back to 32-bit unsigned values and store them. If length is not

+// exactly correct then we flag and return an error.

+

+int read_entropy_vars (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    uchar *byteptr = wpmd->data;

+

+    if (wpmd->byte_length != ((wps->wphdr.flags & MONO_DATA) ? 6 : 12))

+        return FALSE;

+

+    wps->w.c [0].median [0] = exp2s (byteptr [0] + (byteptr [1] << 8));

+    wps->w.c [0].median [1] = exp2s (byteptr [2] + (byteptr [3] << 8));

+    wps->w.c [0].median [2] = exp2s (byteptr [4] + (byteptr [5] << 8));

+

+    if (!(wps->wphdr.flags & MONO_DATA)) {

+        wps->w.c [1].median [0] = exp2s (byteptr [6] + (byteptr [7] << 8));

+        wps->w.c [1].median [1] = exp2s (byteptr [8] + (byteptr [9] << 8));

+        wps->w.c [1].median [2] = exp2s (byteptr [10] + (byteptr [11] << 8));

+    }

+

+    return TRUE;

+}

+

+// Read the hybrid related values from the specifed metadata structure, convert

+// them back to their internal formats and store them. The extended profile

+// stuff is not implemented yet, so return an error if we get more data than

+// we know what to do with.

+

+int read_hybrid_profile (WavpackStream *wps, WavpackMetadata *wpmd)

+{

+    uchar *byteptr = wpmd->data;

+    uchar *endptr = byteptr + wpmd->byte_length;

+

+    if (wps->wphdr.flags & HYBRID_BITRATE) {

+        wps->w.c [0].slow_level = exp2s (byteptr [0] + (byteptr [1] << 8));

+        byteptr += 2;

+

+        if (!(wps->wphdr.flags & MONO_DATA)) {

+            wps->w.c [1].slow_level = exp2s (byteptr [0] + (byteptr [1] << 8));

+            byteptr += 2;

+        }

+    }

+

+    wps->w.bitrate_acc [0] = (int32_t)(byteptr [0] + (byteptr [1] << 8)) << 16;

+    byteptr += 2;

+

+    if (!(wps->wphdr.flags & MONO_DATA)) {

+        wps->w.bitrate_acc [1] = (int32_t)(byteptr [0] + (byteptr [1] << 8)) << 16;

+        byteptr += 2;

+    }

+

+    if (byteptr < endptr) {

+        wps->w.bitrate_delta [0] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+        byteptr += 2;

+

+        if (!(wps->wphdr.flags & MONO_DATA)) {

+            wps->w.bitrate_delta [1] = exp2s ((short)(byteptr [0] + (byteptr [1] << 8)));

+            byteptr += 2;

+        }

+

+        if (byteptr < endptr)

+            return FALSE;

+    }

+    else

+        wps->w.bitrate_delta [0] = wps->w.bitrate_delta [1] = 0;

+

+    return TRUE;

+}

+

+// This function is called during both encoding and decoding of hybrid data to

+// update the "error_limit" variable which determines the maximum sample error

+// allowed in the main bitstream. In the HYBRID_BITRATE mode (which is the only

+// currently implemented) this is calculated from the slow_level values and the

+// bitrate accumulators. Note that the bitrate accumulators can be changing.

+

+void update_error_limit (struct words_data *w, uint32_t flags)

+{

+    int bitrate_0 = (w->bitrate_acc [0] += w->bitrate_delta [0]) >> 16;

+

+    if (flags & MONO_DATA) {

+        if (flags & HYBRID_BITRATE) {

+            int slow_log_0 = (w->c [0].slow_level + SLO) >> SLS;

+

+            if (slow_log_0 - bitrate_0 > -0x100)

+                w->c [0].error_limit = exp2s (slow_log_0 - bitrate_0 + 0x100);

+            else

+                w->c [0].error_limit = 0;

+        }

+        else

+            w->c [0].error_limit = exp2s (bitrate_0);

+    }

+    else {

+        int bitrate_1 = (w->bitrate_acc [1] += w->bitrate_delta [1]) >> 16;

+

+        if (flags & HYBRID_BITRATE) {

+            int slow_log_0 = (w->c [0].slow_level + SLO) >> SLS;

+            int slow_log_1 = (w->c [1].slow_level + SLO) >> SLS;

+

+            if (flags & HYBRID_BALANCE) {

+                int balance = (slow_log_1 - slow_log_0 + bitrate_1 + 1) >> 1;

+

+                if (balance > bitrate_0) {

+                    bitrate_1 = bitrate_0 * 2;

+                    bitrate_0 = 0;

+                }

+                else if (-balance > bitrate_0) {

+                    bitrate_0 = bitrate_0 * 2;

+                    bitrate_1 = 0;

+                }

+                else {

+                    bitrate_1 = bitrate_0 + balance;

+                    bitrate_0 = bitrate_0 - balance;

+                }

+            }

+

+            if (slow_log_0 - bitrate_0 > -0x100)

+                w->c [0].error_limit = exp2s (slow_log_0 - bitrate_0 + 0x100);

+            else

+                w->c [0].error_limit = 0;

+

+            if (slow_log_1 - bitrate_1 > -0x100)

+                w->c [1].error_limit = exp2s (slow_log_1 - bitrate_1 + 0x100);

+            else

+                w->c [1].error_limit = 0;

+        }

+        else {

+            w->c [0].error_limit = exp2s (bitrate_0);

+            w->c [1].error_limit = exp2s (bitrate_1);

+        }

+    }

+}

+

+static uint32_t read_code (Bitstream *bs, uint32_t maxcode);

+

+// Read the next word from the bitstream "wvbits" and return the value. This

+// function can be used for hybrid or lossless streams, but since an

+// optimized version is available for lossless this function would normally

+// be used for hybrid only. If a hybrid lossless stream is being read then

+// the "correction" offset is written at the specified pointer. A return value

+// of WORD_EOF indicates that the end of the bitstream was reached (all 1s) or

+// some other error occurred.

+

+int32_t get_words (int32_t *buffer, int nsamples, uint32_t flags,

+                struct words_data *w, Bitstream *bs)

+{

+    register struct entropy_data *c = w->c;

+    int csamples;

+

+    if (!(flags & MONO_DATA))

+        nsamples *= 2;

+

+    for (csamples = 0; csamples < nsamples; ++csamples) {

+        uint32_t ones_count, low, mid, high;

+

+        if (!(flags & MONO_DATA))

+            c = w->c + (csamples & 1);

+

+        if (!(w->c [0].median [0] & ~1) && !w->holding_zero && !w->holding_one && !(w->c [1].median [0] & ~1)) {

+            uint32_t mask;

+            int cbits;

+

+            if (w->zeros_acc) {

+                if (--w->zeros_acc) {

+                    c->slow_level -= (c->slow_level + SLO) >> SLS;

+                    *buffer++ = 0;

+                    continue;

+                }

+            }

+            else {

+                for (cbits = 0; cbits < 33 && getbit (bs); ++cbits);

+

+                if (cbits == 33)

+                    break;

+

+                if (cbits < 2)

+                    w->zeros_acc = cbits;

+                else {

+                    for (mask = 1, w->zeros_acc = 0; --cbits; mask <<= 1)

+                        if (getbit (bs))

+                            w->zeros_acc |= mask;

+

+                    w->zeros_acc |= mask;

+                }

+

+                if (w->zeros_acc) {

+                    c->slow_level -= (c->slow_level + SLO) >> SLS;

+                    CLEAR (w->c [0].median);

+                    CLEAR (w->c [1].median);

+                    *buffer++ = 0;

+                    continue;

+                }

+            }

+        }

+

+        if (w->holding_zero)

+            ones_count = w->holding_zero = 0;

+        else {

+            int next8;

+

+            if (bs->bc < 8) {

+                if (++(bs->ptr) == bs->end)

+                    bs->wrap (bs);

+

+                next8 = (bs->sr |= *(bs->ptr) << bs->bc) & 0xff;

+                bs->bc += 8;

+            }

+            else

+                next8 = bs->sr & 0xff;

+

+            if (next8 == 0xff) {

+                bs->bc -= 8;

+                bs->sr >>= 8;

+

+                for (ones_count = 8; ones_count < (LIMIT_ONES + 1) && getbit (bs); ++ones_count);

+

+                if (ones_count == (LIMIT_ONES + 1))

+                    break;

+

+                if (ones_count == LIMIT_ONES) {

+                    uint32_t mask;

+                    int cbits;

+

+                    for (cbits = 0; cbits < 33 && getbit (bs); ++cbits);

+

+                    if (cbits == 33)

+                        break;

+

+                    if (cbits < 2)

+                        ones_count = cbits;

+                    else {

+                        for (mask = 1, ones_count = 0; --cbits; mask <<= 1)

+                            if (getbit (bs))

+                                ones_count |= mask;

+

+                        ones_count |= mask;

+                    }

+

+                    ones_count += LIMIT_ONES;

+                }

+            }

+            else {

+                bs->bc -= (ones_count = ones_count_table [next8]) + 1;

+                bs->sr >>= ones_count + 1;

+            }

+

+            if (w->holding_one) {

+                w->holding_one = ones_count & 1;

+                ones_count = (ones_count >> 1) + 1;

+            }

+            else {

+                w->holding_one = ones_count & 1;

+                ones_count >>= 1;

+            }

+

+            w->holding_zero = ~w->holding_one & 1;

+        }

+

+        if ((flags & HYBRID_FLAG) && ((flags & MONO_DATA) || !(csamples & 1)))

+            update_error_limit (w, flags);

+

+        if (ones_count == 0) {

+            low = 0;

+            high = GET_MED (0) - 1;

+            DEC_MED0 ();

+        }

+        else {

+            low = GET_MED (0);

+            INC_MED0 ();

+

+            if (ones_count == 1) {

+                high = low + GET_MED (1) - 1;

+                DEC_MED1 ();

+            }

+            else {

+                low += GET_MED (1);

+                INC_MED1 ();

+

+                if (ones_count == 2) {

+                    high = low + GET_MED (2) - 1;

+                    DEC_MED2 ();

+                }

+                else {

+                    low += (ones_count - 2) * GET_MED (2);

+                    high = low + GET_MED (2) - 1;

+                    INC_MED2 ();

+                }

+            }

+        }

+

+        mid = (high + low + 1) >> 1;

+

+        if (!c->error_limit)

+            mid = read_code (bs, high - low) + low;

+        else while (high - low > c->error_limit) {

+            if (getbit (bs))

+                mid = (high + (low = mid) + 1) >> 1;

+            else

+                mid = ((high = mid - 1) + low + 1) >> 1;

+        }

+

+        *buffer++ = getbit (bs) ? ~mid : mid;

+

+        if (flags & HYBRID_BITRATE)

+            c->slow_level = c->slow_level - ((c->slow_level + SLO) >> SLS) + mylog2 (mid);

+    }

+

+    return (flags & MONO_DATA) ? csamples : (csamples / 2);

+}

+

+// Read a single unsigned value from the specified bitstream with a value

+// from 0 to maxcode. If there are exactly a power of two number of possible

+// codes then this will read a fixed number of bits; otherwise it reads the

+// minimum number of bits and then determines whether another bit is needed

+// to define the code.

+

+static uint32_t read_code (Bitstream *bs, uint32_t maxcode)

+{

+    int bitcount = count_bits (maxcode);

+    uint32_t extras = (1L << bitcount) - maxcode - 1, code;

+

+    if (!bitcount)

+        return 0;

+

+    getbits (&code, bitcount - 1, bs);

+    code &= (1L << (bitcount - 1)) - 1;

+

+    if (code >= extras) {

+        code = (code << 1) - extras;

+

+        if (getbit (bs))

+            ++code;

+    }

+

+    return code;

+}

+

+// The concept of a base 2 logarithm is used in many parts of WavPack. It is

+// a way of sufficiently accurately representing 32-bit signed and unsigned

+// values storing only 16 bits (actually fewer). It is also used in the hybrid

+// mode for quickly comparing the relative magnitude of large values (i.e.

+// division) and providing smooth exponentials using only addition.

+

+// These are not strict logarithms in that they become linear around zero and

+// can therefore represent both zero and negative values. They have 8 bits

+// of precision and in "roundtrip" conversions the total error never exceeds 1

+// part in 225 except for the cases of +/-115 and +/-195 (which error by 1).

+

+

+// This function returns the log2 for the specified 32-bit unsigned value.

+// The maximum value allowed is about 0xff800000 and returns 8447.

+

+static int mylog2 (uint32_t avalue)

+{

+    int dbits;

+

+    if ((avalue += avalue >> 9) < (1 << 8)) {

+        dbits = nbits_table [avalue];

+        return (dbits << 8) + log2_table [(avalue << (9 - dbits)) & 0xff];

+    }

+    else {

+        if (avalue < (1L << 16))

+            dbits = nbits_table [avalue >> 8] + 8;

+        else if (avalue < (1L << 24))

+            dbits = nbits_table [avalue >> 16] + 16;

+        else

+            dbits = nbits_table [avalue >> 24] + 24;

+

+        return (dbits << 8) + log2_table [(avalue >> (dbits - 9)) & 0xff];

+    }

+}

+

+// This function returns the log2 for the specified 32-bit signed value.

+// All input values are valid and the return values are in the range of

+// +/- 8192.

+

+int log2s (int32_t value)

+{

+    return (value < 0) ? -mylog2 (-value) : mylog2 (value);

+}

+

+// This function returns the original integer represented by the supplied

+// logarithm (at least within the provided accuracy). The log is signed,

+// but since a full 32-bit value is returned this can be used for unsigned

+// conversions as well (i.e. the input range is -8192 to +8447).

+

+int32_t exp2s (int log)

+{

+    uint32_t value;

+

+    if (log < 0)

+        return -exp2s (-log);

+

+    value = exp2_table [log & 0xff] | 0x100;

+

+    if ((log >>= 8) <= 9)

+        return value >> (9 - log);

+    else

+        return value << (log - 9);

+}

+

+// These two functions convert internal weights (which are normally +/-1024)

+// to and from an 8-bit signed character version for storage in metadata. The

+// weights are clipped here in the case that they are outside that range.

+

+int restore_weight (signed char weight)

+{

+    int result;

+

+    if ((result = (int) weight << 3) > 0)

+        result += (result + 64) >> 7;

+

+    return result;

+}

diff --git a/src/wavpack/wputils.c b/src/wavpack/wputils.c
new file mode 100644
index 00000000..0d71af53
--- /dev/null
+++ b/src/wavpack/wputils.c
@@ -0,0 +1,351 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// wputils.c

+

+// This module provides a high-level interface for decoding WavPack 4.0 audio

+// streams and files. WavPack data is read with a stream reading callback. No

+// direct seeking is provided for, but it is possible to start decoding

+// anywhere in a WavPack stream. In this case, WavPack will be able to provide

+// the sample-accurate position when it synchs with the data and begins

+// decoding.

+

+#include "wavpack.h"

+

+#include <string.h>

+

+///////////////////////////// local table storage ////////////////////////////

+

+const uint32_t sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050,

+    24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 };

+

+///////////////////////////// executable code ////////////////////////////////

+

+static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr);

+        

+// This function reads data from the specified stream in search of a valid

+// WavPack 4.0 audio block. If this fails in 1 megabyte (or an invalid or

+// unsupported WavPack block is encountered) then an appropriate message is

+// copied to "error" and NULL is returned, otherwise a pointer to a

+// WavpackContext structure is returned (which is used to call all other

+// functions in this module). This can be initiated at the beginning of a

+// WavPack file, or anywhere inside a WavPack file. To determine the exact

+// position within the file use WavpackGetSampleIndex(). For demonstration

+// purposes this uses a single static copy of the WavpackContext structure,

+// so obviously it cannot be used for more than one file at a time. Also,

+// this function will not handle "correction" files, plays only the first

+// two channels of multi-channel files, and is limited in resolution in some

+// large integer or floating point files (but always provides at least 24 bits

+// of resolution).

+

+static WavpackContext wpc;

+

+WavpackContext *WavpackOpenFileInput (read_stream infile, char *error)

+{

+    WavpackStream *wps = &wpc.stream;

+    uint32_t bcount;

+

+    CLEAR (wpc);

+    wpc.infile = infile;

+    wpc.total_samples = (uint32_t) -1;

+    wpc.norm_offset = 0;

+    wpc.open_flags = 0;

+

+    // open the source file for reading and store the size

+

+    while (!wps->wphdr.block_samples) {

+

+        bcount = read_next_header (wpc.infile, &wps->wphdr);

+

+        if (bcount == (uint32_t) -1) {

+            strcpy (error, "not compatible with this version of WavPack file!");

+            return NULL;

+        }

+

+        if (wps->wphdr.block_samples && wps->wphdr.total_samples != (uint32_t) -1)

+            wpc.total_samples = wps->wphdr.total_samples;

+

+        if (!unpack_init (&wpc)) {

+            strcpy (error, wpc.error_message [0] ? wpc.error_message :

+                "not compatible with this version of WavPack file!");

+

+            return NULL;

+        }

+    }

+

+    wpc.config.flags &= ~0xff;

+    wpc.config.flags |= wps->wphdr.flags & 0xff;

+    wpc.config.bytes_per_sample = (wps->wphdr.flags & BYTES_STORED) + 1;

+    wpc.config.float_norm_exp = wps->float_norm_exp;

+

+    wpc.config.bits_per_sample = (wpc.config.bytes_per_sample * 8) - 

+        ((wps->wphdr.flags & SHIFT_MASK) >> SHIFT_LSB);

+

+    if (wpc.config.flags & FLOAT_DATA) {

+        wpc.config.bytes_per_sample = 3;

+        wpc.config.bits_per_sample = 24;

+    }

+

+    if (!wpc.config.sample_rate) {

+        if (!wps || !wps->wphdr.block_samples || (wps->wphdr.flags & SRATE_MASK) == SRATE_MASK)

+            wpc.config.sample_rate = 44100;

+        else

+            wpc.config.sample_rate = sample_rates [(wps->wphdr.flags & SRATE_MASK) >> SRATE_LSB];

+    }

+

+    if (!wpc.config.num_channels) {

+        wpc.config.num_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2;

+        wpc.config.channel_mask = 0x5 - wpc.config.num_channels;

+    }

+

+    if (!(wps->wphdr.flags & FINAL_BLOCK))

+        wpc.reduced_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2;

+

+    return &wpc;

+}

+

+// This function obtains general information about an open file and returns

+// a mask with the following bit values:

+

+// MODE_LOSSLESS:  file is lossless (pure lossless only)

+// MODE_HYBRID:  file is hybrid mode (lossy part only)

+// MODE_FLOAT:  audio data is 32-bit ieee floating point (but will provided

+//               in 24-bit integers for convenience)

+// MODE_HIGH:  file was created in "high" mode (information only)

+// MODE_FAST:  file was created in "fast" mode (information only)

+

+int WavpackGetMode (WavpackContext *wpc)

+{

+    int mode = 0;

+

+    if (wpc) {

+        if (wpc->config.flags & CONFIG_HYBRID_FLAG)

+            mode |= MODE_HYBRID;

+        else if (!(wpc->config.flags & CONFIG_LOSSY_MODE))

+            mode |= MODE_LOSSLESS;

+

+        if (wpc->lossy_blocks)

+            mode &= ~MODE_LOSSLESS;

+

+        if (wpc->config.flags & CONFIG_FLOAT_DATA)

+            mode |= MODE_FLOAT;

+

+        if (wpc->config.flags & CONFIG_HIGH_FLAG)

+            mode |= MODE_HIGH;

+

+        if (wpc->config.flags & CONFIG_FAST_FLAG)

+            mode |= MODE_FAST;

+    }

+

+    return mode;

+}

+

+// Unpack the specified number of samples from the current file position.

+// Note that "samples" here refers to "complete" samples, which would be

+// 2 longs for stereo files. The audio data is returned right-justified in

+// 32-bit longs in the endian mode native to the executing processor. So,

+// if the original data was 16-bit, then the values returned would be

+// +/-32k. Floating point data will be returned as 24-bit integers (and may

+// also be clipped). The actual number of samples unpacked is returned,

+// which should be equal to the number requested unless the end of fle is

+// encountered or an error occurs.

+

+uint32_t WavpackUnpackSamples (WavpackContext *wpc, int32_t *buffer, uint32_t samples)

+{

+    WavpackStream *wps = &wpc->stream;

+    uint32_t bcount, samples_unpacked = 0, samples_to_unpack;

+    int num_channels = wpc->config.num_channels;

+

+    while (samples) {

+        if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) ||

+            wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples) {

+                bcount = read_next_header (wpc->infile, &wps->wphdr);

+

+                if (bcount == (uint32_t) -1)

+                    break;

+

+                if (!wps->wphdr.block_samples || wps->sample_index == wps->wphdr.block_index)

+                    if (!unpack_init (wpc))

+                        break;

+        }

+

+        if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) ||

+            wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples)

+                continue;

+

+        if (wps->sample_index < wps->wphdr.block_index) {

+            samples_to_unpack = wps->wphdr.block_index - wps->sample_index;

+

+            if (samples_to_unpack > samples)

+                samples_to_unpack = samples;

+

+            wps->sample_index += samples_to_unpack;

+            samples_unpacked += samples_to_unpack;

+            samples -= samples_to_unpack;

+

+            if (wpc->reduced_channels)

+                samples_to_unpack *= wpc->reduced_channels;

+            else

+                samples_to_unpack *= num_channels;

+

+            while (samples_to_unpack--)

+                *buffer++ = 0;

+

+            continue;

+        }

+

+        samples_to_unpack = wps->wphdr.block_index + wps->wphdr.block_samples - wps->sample_index;

+

+        if (samples_to_unpack > samples)

+            samples_to_unpack = samples;

+

+        unpack_samples (wpc, buffer, samples_to_unpack);

+

+        if (wpc->reduced_channels)

+            buffer += samples_to_unpack * wpc->reduced_channels;

+        else

+            buffer += samples_to_unpack * num_channels;

+

+        samples_unpacked += samples_to_unpack;

+        samples -= samples_to_unpack;

+

+        if (wps->sample_index == wps->wphdr.block_index + wps->wphdr.block_samples) {

+            if (check_crc_error (wpc))

+                wpc->crc_errors++;

+        }

+

+        if (wps->sample_index == wpc->total_samples)

+            break;

+    }

+

+    return samples_unpacked;

+}

+

+// Get total number of samples contained in the WavPack file, or -1 if unknown

+

+uint32_t WavpackGetNumSamples (WavpackContext *wpc)

+{

+    return wpc ? wpc->total_samples : (uint32_t) -1;

+}

+

+// Get the current sample index position, or -1 if unknown

+

+uint32_t WavpackGetSampleIndex (WavpackContext *wpc)

+{

+    if (wpc)

+        return wpc->stream.sample_index;

+

+    return (uint32_t) -1;

+}

+

+// Get the number of errors encountered so far

+

+int WavpackGetNumErrors (WavpackContext *wpc)

+{

+    return wpc ? wpc->crc_errors : 0;

+}

+

+// return TRUE if any uncorrected lossy blocks were actually written or read

+

+int WavpackLossyBlocks (WavpackContext *wpc)

+{

+    return wpc ? wpc->lossy_blocks : 0;

+}

+

+// Returns the sample rate of the specified WavPack file

+

+uint32_t WavpackGetSampleRate (WavpackContext *wpc)

+{

+    return wpc ? wpc->config.sample_rate : 44100;

+}

+

+// Returns the number of channels of the specified WavPack file. Note that

+// this is the actual number of channels contained in the file, but this

+// version can only decode the first two.

+

+int WavpackGetNumChannels (WavpackContext *wpc)

+{

+    return wpc ? wpc->config.num_channels : 2;

+}

+

+// Returns the actual number of valid bits per sample contained in the

+// original file, which may or may not be a multiple of 8. Floating data

+// always has 32 bits, integers may be from 1 to 32 bits each. When this

+// value is not a multiple of 8, then the "extra" bits are located in the

+// LSBs of the results. That is, values are right justified when unpacked

+// into longs, but are left justified in the number of bytes used by the

+// original data.

+

+int WavpackGetBitsPerSample (WavpackContext *wpc)

+{

+    return wpc ? wpc->config.bits_per_sample : 16;

+}

+

+// Returns the number of bytes used for each sample (1 to 4) in the original

+// file. This is required information for the user of this module because the

+// audio data is returned in the LOWER bytes of the long buffer and must be

+// left-shifted 8, 16, or 24 bits if normalized longs are required.

+

+int WavpackGetBytesPerSample (WavpackContext *wpc)

+{

+    return wpc ? wpc->config.bytes_per_sample : 2;

+}

+

+// This function will return the actual number of channels decoded from the

+// file (which may or may not be less than the actual number of channels, but

+// will always be 1 or 2). Normally, this will be the front left and right

+// channels of a multi-channel file.

+

+int WavpackGetReducedChannels (WavpackContext *wpc)

+{

+    if (wpc)

+        return wpc->reduced_channels ? wpc->reduced_channels : wpc->config.num_channels;

+    else

+        return 2;

+}

+

+// Read from current file position until a valid 32-byte WavPack 4.0 header is

+// found and read into the specified pointer. The number of bytes skipped is

+// returned. If no WavPack header is found within 1 meg, then a -1 is returned

+// to indicate the error. No additional bytes are read past the header and it

+// is returned in the processor's native endian mode. Seeking is not required.

+

+static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr)

+{

+    char buffer [sizeof (*wphdr)], *sp = buffer + sizeof (*wphdr), *ep = sp;

+    uint32_t bytes_skipped = 0;

+    int bleft;

+

+    while (1) {

+        if (sp < ep) {

+            bleft = ep - sp;

+            memcpy (buffer, sp, bleft);

+        }

+        else

+            bleft = 0;

+

+        if (infile (buffer + bleft, sizeof (*wphdr) - bleft) != (int32_t) sizeof (*wphdr) - bleft)

+            return -1;

+

+        sp = buffer;

+

+        if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' &&

+            !(*++sp & 1) && sp [2] < 16 && !sp [3] && sp [5] == 4 &&

+            sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff)) {

+                memcpy (wphdr, buffer, sizeof (*wphdr));

+                little_endian_to_native (wphdr, WavpackHeaderFormat);

+                return bytes_skipped;

+            }

+

+        while (sp < ep && *sp != 'w')

+            sp++;

+

+        if ((bytes_skipped += sp - buffer) > 1048576L)

+            return -1;

+    }

+}

diff --git a/src/wavpack/wvfilter.c.no_compile b/src/wavpack/wvfilter.c.no_compile
new file mode 100644
index 00000000..f80d73dd
--- /dev/null
+++ b/src/wavpack/wvfilter.c.no_compile
@@ -0,0 +1,200 @@
+////////////////////////////////////////////////////////////////////////////

+//                           **** WAVPACK ****                            //

+//                  Hybrid Lossless Wavefile Compressor                   //

+//              Copyright (c) 1998 - 2006 Conifer Software.               //

+//                          All Rights Reserved.                          //

+//      Distributed under the BSD Software License (see license.txt)      //

+////////////////////////////////////////////////////////////////////////////

+

+// wv_filter.c

+

+// This is the main module for the demonstration WavPack command-line

+// decoder filter. It uses the tiny "hardware" version of the decoder and

+// accepts WavPack files on stdin and outputs a standard MS wav file to

+// stdout. Note that this involves converting the data to little-endian

+// (if the executing processor is not), possibly packing the data into

+// fewer bytes per sample, and generating an appropriate riff wav header.

+// Note that this is NOT the copy of the RIFF header that might be stored

+// in the file, and any additional RIFF information and tags are lost.

+// See wputils.c for further limitations.

+

+#include "wavpack.h"

+

+#if defined(WIN32)

+#include <io.h>

+#include <fcntl.h>

+#endif

+

+#include <string.h>

+

+// These structures are used to place a wav riff header at the beginning of

+// the output.

+

+typedef struct {

+    char ckID [4];

+    uint32_t ckSize;

+    char formType [4];

+} RiffChunkHeader;

+

+typedef struct {

+    char ckID [4];

+    uint32_t ckSize;

+} ChunkHeader;

+

+#define ChunkHeaderFormat "4L"

+

+typedef struct {

+    ushort FormatTag, NumChannels;

+    uint32_t SampleRate, BytesPerSecond;

+    ushort BlockAlign, BitsPerSample;

+} WaveHeader;

+

+#define WaveHeaderFormat "SSLLSS"

+

+static uchar *format_samples (int bps, uchar *dst, int32_t *src, uint32_t samcnt);

+static int32_t read_bytes (void *buff, int32_t bcount);

+static int32_t temp_buffer [256];

+

+int main ()

+{

+    ChunkHeader FormatChunkHeader, DataChunkHeader;

+    RiffChunkHeader RiffChunkHeader;

+    WaveHeader WaveHeader;

+

+    uint32_t total_unpacked_samples = 0, total_samples;

+    int num_channels, bps;

+    WavpackContext *wpc;

+    char error [80];

+

+#if defined(WIN32)

+    setmode (fileno (stdin), O_BINARY);

+    setmode (fileno (stdout), O_BINARY);

+#endif

+

+    wpc = WavpackOpenFileInput (read_bytes, error);

+

+    if (!wpc) {

+        fputs (error, stderr);

+        fputs ("\n", stderr);

+        return 1;

+    }

+

+    num_channels = WavpackGetReducedChannels (wpc);

+    total_samples = WavpackGetNumSamples (wpc);

+    bps = WavpackGetBytesPerSample (wpc);

+

+    strncpy (RiffChunkHeader.ckID, "RIFF", sizeof (RiffChunkHeader.ckID));

+    RiffChunkHeader.ckSize = total_samples * num_channels * bps + sizeof (ChunkHeader) * 2 + sizeof (WaveHeader) + 4;

+    strncpy (RiffChunkHeader.formType, "WAVE", sizeof (RiffChunkHeader.formType));

+

+    strncpy (FormatChunkHeader.ckID, "fmt ", sizeof (FormatChunkHeader.ckID));

+    FormatChunkHeader.ckSize = sizeof (WaveHeader);

+

+    WaveHeader.FormatTag = 1;

+    WaveHeader.NumChannels = num_channels;

+    WaveHeader.SampleRate = WavpackGetSampleRate (wpc);

+    WaveHeader.BlockAlign = num_channels * bps;

+    WaveHeader.BytesPerSecond = WaveHeader.SampleRate * WaveHeader.BlockAlign;

+    WaveHeader.BitsPerSample = WavpackGetBitsPerSample (wpc);

+

+    strncpy (DataChunkHeader.ckID, "data", sizeof (DataChunkHeader.ckID));

+    DataChunkHeader.ckSize = total_samples * num_channels * bps;

+

+    native_to_little_endian (&RiffChunkHeader, ChunkHeaderFormat);

+    native_to_little_endian (&FormatChunkHeader, ChunkHeaderFormat);

+    native_to_little_endian (&WaveHeader, WaveHeaderFormat);

+    native_to_little_endian (&DataChunkHeader, ChunkHeaderFormat);

+

+    if (!fwrite (&RiffChunkHeader, sizeof (RiffChunkHeader), 1, stdout) ||

+        !fwrite (&FormatChunkHeader, sizeof (FormatChunkHeader), 1, stdout) ||

+        !fwrite (&WaveHeader, sizeof (WaveHeader), 1, stdout) ||

+        !fwrite (&DataChunkHeader, sizeof (DataChunkHeader), 1, stdout)) {

+            fputs ("can't write .WAV data, disk probably full!\n", stderr);

+            return 1;

+        }

+

+    while (1) {

+        uint32_t samples_unpacked;

+

+        samples_unpacked = WavpackUnpackSamples (wpc, temp_buffer, 256 / num_channels);

+        total_unpacked_samples += samples_unpacked;

+

+        if (samples_unpacked) {

+            format_samples (bps, (uchar *) temp_buffer, temp_buffer, samples_unpacked *= num_channels);

+

+            if (fwrite (temp_buffer, bps, samples_unpacked, stdout) != samples_unpacked) {

+                fputs ("can't write .WAV data, disk probably full!\n", stderr);

+                return 1;

+            }

+        }

+

+        if (!samples_unpacked)

+            break;

+    }

+

+    fflush (stdout);

+

+    if (WavpackGetNumSamples (wpc) != (uint32_t) -1 &&

+        total_unpacked_samples != WavpackGetNumSamples (wpc)) {

+            fputs ("incorrect number of samples!\n", stderr);

+            return 1;

+    }

+

+    if (WavpackGetNumErrors (wpc)) {

+        fputs ("crc errors detected!\n", stderr);

+        return 1;

+    }

+

+    return 0;

+}

+

+static int32_t read_bytes (void *buff, int32_t bcount)

+{

+    return fread (buff, 1, bcount, stdin);

+}

+

+// Reformat samples from longs in processor's native endian mode to

+// little-endian data with (possibly) less than 4 bytes / sample.

+

+static uchar *format_samples (int bps, uchar *dst, int32_t *src, uint32_t samcnt)

+{

+    int32_t temp;

+

+    switch (bps) {

+

+        case 1:

+            while (samcnt--)

+                *dst++ = *src++ + 128;

+

+            break;

+

+        case 2:

+            while (samcnt--) {

+                *dst++ = (uchar)(temp = *src++);

+                *dst++ = (uchar)(temp >> 8);

+            }

+

+            break;

+

+        case 3:

+            while (samcnt--) {

+                *dst++ = (uchar)(temp = *src++);

+                *dst++ = (uchar)(temp >> 8);

+                *dst++ = (uchar)(temp >> 16);

+            }

+

+            break;

+

+        case 4:

+            while (samcnt--) {

+                *dst++ = (uchar)(temp = *src++);

+                *dst++ = (uchar)(temp >> 8);

+                *dst++ = (uchar)(temp >> 16);

+                *dst++ = (uchar)(temp >> 24);

+            }

+

+            break;

+    }

+

+    return dst;

+}