about summary refs log tree commit diff
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/client/ec_client.c64
-rw-r--r--src/engine/docs/client_time.txt7
-rw-r--r--src/engine/e_demorec.c378
-rw-r--r--src/engine/e_demorec.h12
-rw-r--r--src/engine/e_network.c11
-rw-r--r--src/engine/e_network.h2
-rw-r--r--src/engine/e_snapshot.c7
-rw-r--r--src/engine/server/es_server.c10
8 files changed, 380 insertions, 111 deletions
diff --git a/src/engine/client/ec_client.c b/src/engine/client/ec_client.c
index ece501ab..9c798c30 100644
--- a/src/engine/client/ec_client.c
+++ b/src/engine/client/ec_client.c
@@ -323,7 +323,7 @@ int client_send_msg()
 	if(info->flags&MSGFLAG_RECORD)
 	{
 		if(demorec_isrecording())
-			demorec_record_write("MESG", packet.data_size, packet.data);
+			demorec_record_message(packet.data, packet.data_size);
 	}
 
 	if(!(info->flags&MSGFLAG_NOSEND))
@@ -1135,15 +1135,18 @@ static void client_process_packet(NETCHUNK *packet)
 						/* add snapshot to demo */
 						if(demorec_isrecording())
 						{
-							DEMOREC_TICKMARKER marker;
 
 							/* write tick marker */
+							/*
+							DEMOREC_TICKMARKER marker;
 							marker.tick = game_tick;
 							swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int));
 							demorec_record_write("TICK", sizeof(marker), &marker);
+							demorec_record_write("SNAP", snapsize, tmpbuffer3);
+							*/
 							
 							/* write snapshot */
-							demorec_record_write("SNAP", snapsize, tmpbuffer3);							
+							demorec_record_snapshot(game_tick, tmpbuffer3, snapsize);
 						}
 						
 						/* apply snapshot, cycle pointers */
@@ -1187,7 +1190,8 @@ static void client_process_packet(NETCHUNK *packet)
 		{
 			/* game message */
 			if(demorec_isrecording())
-				demorec_record_write("MESG", packet->data_size, packet->data);
+				demorec_record_message(packet->data, packet->data_size);
+				/* demorec_record_write("MESG", packet->data_size, ); */
 
 			modc_message(msg);
 		}
@@ -1227,38 +1231,35 @@ static void client_pump_network()
 		client_process_packet(&packet);
 }
 
-static void client_democallback(DEMOREC_CHUNK chunk, void *data)
+static void client_democallback_snapshot(void *data, int size)
 {
-	/* dbg_msg("client/playback", "got %c%c%c%c", chunk.type[0], chunk.type[1], chunk.type[2], chunk.type[3]); */
-
 	/* update ticks, they could have changed */
 	const DEMOREC_PLAYBACKINFO *info = demorec_playback_info();			
+	SNAPSTORAGE_HOLDER *temp;
 	current_tick = info->current_tick;
 	prev_tick = info->previous_tick;
 	
-	if(mem_comp(chunk.type, "SNAP", 4) == 0)
-	{
-		/* handle snapshots */
-		SNAPSTORAGE_HOLDER *temp = snapshots[SNAP_PREV];
-		snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
-		snapshots[SNAP_CURRENT] = temp;
-		
-		mem_copy(snapshots[SNAP_CURRENT]->snap, data, chunk.size);
-		mem_copy(snapshots[SNAP_CURRENT]->alt_snap, data, chunk.size);
-		
-		modc_newsnapshot();
-		modc_predict();
-	}
-	else if(mem_comp(chunk.type, "MESG", 4) == 0)
-	{
-		/* handle messages */
-		int sys = 0;
-		int msg = msg_unpack_start(data, chunk.size, &sys);
-		if(!sys)
-			modc_message(msg);
-	}
+	/* handle snapshots */
+	temp = snapshots[SNAP_PREV];
+	snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT];
+	snapshots[SNAP_CURRENT] = temp;
+	
+	mem_copy(snapshots[SNAP_CURRENT]->snap, data, size);
+	mem_copy(snapshots[SNAP_CURRENT]->alt_snap, data, size);
+	
+	modc_newsnapshot();
+	/*modc_predict();*/
+}
+
+static void client_democallback_message(void *data, int size)
+{
+	int sys = 0;
+	int msg = msg_unpack_start(data, size, &sys);
+	if(!sys)
+		modc_message(msg);
 }
 
+
 const DEMOPLAYBACK_INFO *client_demoplayer_getinfo()
 {
 	static DEMOPLAYBACK_INFO ret;
@@ -1358,8 +1359,8 @@ static void client_update()
 			int new_pred_tick = prev_pred_tick+1;
 			static float last_predintra = 0;
 
-			intratick = (now - curtick_start) / (float)(curtick_start-prevtick_start);
-			ticktime = (now - curtick_start) / (freq/(float)SERVER_TICK_SPEED);
+			intratick = (now - prevtick_start) / (float)(curtick_start-prevtick_start);
+			ticktime = (now - prevtick_start) / (float)freq; /*(float)SERVER_TICK_SPEED);*/
 
 			graph_add(&intra_graph, intratick*0.25f);
 
@@ -1714,7 +1715,7 @@ void client_demoplayer_play(const char *filename)
 	client_disconnect();
 	
 	/* try to start playback */
-	demorec_playback_registercallback(client_democallback);
+	demorec_playback_registercallbacks(client_democallback_snapshot, client_democallback_message);
 	
 	if(demorec_playback_load(filename))
 		return;
@@ -1803,6 +1804,7 @@ int main(int argc, char **argv)
 	dbg_msg("client", "starting...");
 	engine_init("Teeworlds");
 	
+	
 	/* register all console commands */
 	client_register_commands();
 	modc_console_init();
diff --git a/src/engine/docs/client_time.txt b/src/engine/docs/client_time.txt
index 24ca2be5..bc76f91c 100644
--- a/src/engine/docs/client_time.txt
+++ b/src/engine/docs/client_time.txt
@@ -2,3 +2,10 @@ Title: Time on the client
 
 tick, intratick
 predtick, predintratick
+
+ prevtick              tick                 predtick
+   4                     8                    14
+   |---------------------|---------------------|
+   0 <- intratick    ->  1                     
+   0 <- ticktime(in s)-> X
+                         0 <- predintratick?-> 1
diff --git a/src/engine/e_demorec.c b/src/engine/e_demorec.c
index 1fae2b77..07d7d628 100644
--- a/src/engine/e_demorec.c
+++ b/src/engine/e_demorec.c
@@ -2,12 +2,18 @@
 #include <base/system.h>
 #include "e_demorec.h"
 #include "e_memheap.h"
+#include "e_snapshot.h"
+#include "e_compression.h"
+#include "e_network.h"
 #include "e_if_other.h"
 
 static IOHANDLE record_file = 0;
 static const unsigned char header_marker[8] = {'T', 'W', 'D', 'E', 'M', 'O', 0, 1};
 
 /* Record */
+static int record_lasttickmarker = -1;
+static int record_lastkeyframe;
+static unsigned char record_lastsnapshotdata[MAX_SNAPSHOT_SIZE];
 
 int demorec_isrecording() { return record_file != 0; }
 
@@ -37,24 +43,143 @@ int demorec_record_start(const char *filename, const char *netversion, const cha
 	header.crc[3] = (crc)&0xff;
 	io_write(record_file, &header, sizeof(header));
 	
+	record_lastkeyframe = -1;
+	record_lasttickmarker = -1;
+	
 	dbg_msg("demorec/record", "Recording to '%s'", filename);
 	return 0;
 }
 
-void demorec_record_write(const char *type, int size, const void *data)
+/*
+	Tickmarker
+		7   = Always set
+		6   = Keyframe flag
+		0-5 = Delta tick
+	
+	Normal
+		7   = Not set
+		5-6 = Type
+		0-4 = Size
+*/
+
+enum
+{
+	CHUNKTYPEFLAG_TICKMARKER = 0x80,
+	CHUNKTICKFLAG_KEYFRAME = 0x40, /* only when tickmarker is set*/
+	
+	CHUNKMASK_TICK = 0x3f,
+	CHUNKMASK_TYPE = 0x60,
+	CHUNKMASK_SIZE = 0x1f,
+	
+	CHUNKTYPE_SNAPSHOT = 1,
+	CHUNKTYPE_MESSAGE = 2,
+	CHUNKTYPE_DELTA = 3,
+
+	CHUNKFLAG_BIGSIZE = 0x10
+};
+
+static void demorec_record_write_tickmarker(int tick, int keyframe)
 {
-	DEMOREC_CHUNK chunk;
+	if(record_lasttickmarker == -1 || tick-record_lasttickmarker > 63 || keyframe)
+	{
+		unsigned char chunk[5];
+		chunk[0] = CHUNKTYPEFLAG_TICKMARKER;
+		chunk[1] = (tick>>24)&0xff;
+		chunk[2] = (tick>>16)&0xff;
+		chunk[3] = (tick>>8)&0xff;
+		chunk[4] = (tick)&0xff;
+
+		if(keyframe)
+			chunk[0] |= CHUNKTICKFLAG_KEYFRAME;
+		
+		io_write(record_file, chunk, sizeof(chunk));
+	}
+	else
+	{
+		unsigned char chunk[1];
+		chunk[0] = CHUNKTYPEFLAG_TICKMARKER | (tick-record_lasttickmarker);
+		io_write(record_file, chunk, sizeof(chunk));
+	}	
+
+	record_lasttickmarker = tick;
+}
+
+static void demorec_record_write(int type, const void *data, int size)
+{
+	char buffer[64*1024];
+	char buffer2[64*1024];
+	unsigned char chunk[3];
+	
 	if(!record_file)
 		return;
+		
+	size = intpack_compress(data, size, buffer);
+	size = netcommon_compress(buffer, size, buffer2, sizeof(buffer2));
+	
+	
+	chunk[0] = ((type&0x3)<<5);
+	if(size < 30)
+	{
+		chunk[0] |= size;
+		io_write(record_file, chunk, 1);
+	}
+	else
+	{
+		if(size < 256)
+		{
+			chunk[0] |= 30;
+			chunk[1] = size&0xff;
+			io_write(record_file, chunk, 2);
+		}
+		else
+		{
+			chunk[0] |= 31;
+			chunk[1] = size&0xff;
+			chunk[2] = size>>8;
+			io_write(record_file, chunk, 3);
+		}
+	}
 	
-	chunk.type[0] = type[0];
-	chunk.type[1] = type[1];
-	chunk.type[2] = type[2];
-	chunk.type[3] = type[3];
-	chunk.size = size;
-	swap_endian(&chunk.size, sizeof(int), 1);
-	io_write(record_file, &chunk, sizeof(chunk));
-	io_write(record_file, data, size);
+	io_write(record_file, buffer2, size);
+}
+
+void demorec_record_snapshot(int tick, const void *data, int size)
+{
+	record_emptydeltatick = -1;
+	
+	if(record_lastkeyframe == -1 || (tick-record_lastkeyframe) > SERVER_TICK_SPEED*5)
+	{
+		/* write full tickmarker */
+		demorec_record_write_tickmarker(tick, 1);
+		
+		/* write snapshot */
+		demorec_record_write(CHUNKTYPE_SNAPSHOT, data, size);
+			
+		record_lastkeyframe = tick;
+		mem_copy(record_lastsnapshotdata, data, size);
+	}
+	else
+	{
+		/* create delta, prepend tick */
+		char delta_data[MAX_SNAPSHOT_SIZE+sizeof(int)];
+		int delta_size;
+
+		/* write tickmarker */
+		demorec_record_write_tickmarker(tick, 0);
+		
+		delta_size = snapshot_create_delta((SNAPSHOT*)record_lastsnapshotdata, (SNAPSHOT*)data, &delta_data);
+		if(delta_size)
+		{
+			/* record delta */
+			demorec_record_write(CHUNKTYPE_DELTA, delta_data, delta_size);
+			mem_copy(record_lastsnapshotdata, data, size);
+		}
+	}
+}
+
+void demorec_record_message(const void *data, int size)
+{
+	demorec_record_write(CHUNKTYPE_MESSAGE, data, size);
 }
 
 int demorec_record_stop()
@@ -69,7 +194,6 @@ int demorec_record_stop()
 }
 
 /* Playback */
-
 typedef struct KEYFRAME
 {
 	long filepos;
@@ -83,25 +207,77 @@ typedef struct KEYFRAME_SEARCH
 } KEYFRAME_SEARCH;
 
 static IOHANDLE play_file = 0;
-static DEMOREC_PLAYCALLBACK play_callback = 0;
+static DEMOREC_PLAYCALLBACK play_callback_snapshot = 0;
+static DEMOREC_PLAYCALLBACK play_callback_message = 0;
 static KEYFRAME *keyframes = 0;
 
 static DEMOREC_PLAYBACKINFO playbackinfo;
-const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; }
+static unsigned char playback_lastsnapshotdata[MAX_SNAPSHOT_SIZE];
+static int playback_lastsnapshotdata_size = -1;
 
+
+const DEMOREC_PLAYBACKINFO *demorec_playback_info() { return &playbackinfo; }
 int demorec_isplaying() { return play_file != 0; }
 
-int demorec_playback_registercallback(DEMOREC_PLAYCALLBACK cb)
+int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb)
 {
-	play_callback = cb;
+	play_callback_snapshot = snapshot_cb;
+	play_callback_message = message_cb;
 	return 0;
 }
 
-static int read_chunk_header(DEMOREC_CHUNK *chunk)
+static int read_chunk_header(int *type, int *size, int *tick)
 {
-	if(io_read(play_file, chunk, sizeof(*chunk)) != sizeof(*chunk))
+	unsigned char chunk = 0;
+	
+	*size = 0;
+	*type = 0;
+	
+	if(io_read(play_file, &chunk, sizeof(chunk)) != sizeof(chunk))
 		return -1;
-	swap_endian(&chunk->size, sizeof(int), 1);
+		
+	if(chunk&CHUNKTYPEFLAG_TICKMARKER)
+	{
+		/* decode tick marker */
+		int tickdelta = chunk&(CHUNKMASK_TICK);
+		*type = chunk&(CHUNKTYPEFLAG_TICKMARKER|CHUNKTICKFLAG_KEYFRAME);
+		
+		if(tickdelta == 0)
+		{
+			unsigned char tickdata[4];
+			if(io_read(play_file, tickdata, sizeof(tickdata)) != sizeof(tickdata))
+				return -1;
+			*tick = (tickdata[0]<<24) | (tickdata[1]<<16) | (tickdata[2]<<8) | tickdata[3];
+		}
+		else
+		{
+			*tick += tickdelta;
+		}
+		
+	}
+	else
+	{
+		/* decode normal chunk */
+		*type = (chunk&CHUNKMASK_TYPE)>>5;
+		*size = chunk&CHUNKMASK_SIZE;
+		
+		if(*size == 30)
+		{
+			unsigned char sizedata[1];
+			if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata))
+				return -1;
+			*size = sizedata[0];
+			
+		}
+		else if(*size == 31)
+		{
+			unsigned char sizedata[2];
+			if(io_read(play_file, sizedata, sizeof(sizedata)) != sizeof(sizedata))
+				return -1;
+			*size = (sizedata[1]<<8) | sizedata[0];
+		}
+	}
+	
 	return 0;
 }
 
@@ -111,7 +287,8 @@ static void scan_file()
 	HEAP *heap = 0;
 	KEYFRAME_SEARCH *first_key = 0;
 	KEYFRAME_SEARCH *current_key = 0;
-	DEMOREC_CHUNK chunk;
+	/*DEMOREC_CHUNK chunk;*/
+	int chunk_size, chunk_type, chunk_tick = 0;
 	int i;
 	
 	heap = memheap_create();
@@ -122,37 +299,38 @@ static void scan_file()
 	while(1)
 	{
 		long current_pos = io_tell(play_file);
-
-		if(read_chunk_header(&chunk))
+		
+		if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick))
 			break;
 		
 		/* read the chunk */
-			
-		if(mem_comp(chunk.type, "TICK", 4) == 0)
+		if(chunk_type&CHUNKTYPEFLAG_TICKMARKER)
 		{
-			KEYFRAME_SEARCH *key;
-			DEMOREC_TICKMARKER marker;
-			io_read(play_file, &marker, chunk.size);
-			swap_endian(&marker.tick, sizeof(int), 1);
-			
-			/* save the position */
-			key = memheap_allocate(heap, sizeof(KEYFRAME_SEARCH));
-			key->frame.filepos = current_pos;
-			key->frame.tick = marker.tick;
-			key->next = 0;
-			if(current_key)
-				current_key->next = key;
-			if(!first_key)
-				first_key = key;
-			current_key = key;
+			if(chunk_type&CHUNKTICKFLAG_KEYFRAME)
+			{
+				KEYFRAME_SEARCH *key;
+				
+				/* save the position */
+				key = memheap_allocate(heap, sizeof(KEYFRAME_SEARCH));
+				key->frame.filepos = current_pos;
+				key->frame.tick = chunk_tick;
+				key->next = 0;
+				if(current_key)
+					current_key->next = key;
+				if(!first_key)
+					first_key = key;
+				current_key = key;
+				playbackinfo.seekable_points++;
+			}
 			
 			if(playbackinfo.first_tick == -1)
-				playbackinfo.first_tick = marker.tick;
-			playbackinfo.last_tick = marker.tick;
-			playbackinfo.seekable_points++;
+				playbackinfo.first_tick = chunk_tick;
+			playbackinfo.last_tick = chunk_tick;
+			dbg_msg("", "%x %d", chunk_type, chunk_tick);
 		}
-		else
-			io_skip(play_file, chunk.size);
+		else if(chunk_size)
+			io_skip(play_file, chunk_size);
+			
 	}
 
 	/* copy all the frames to an array instead for fast access */
@@ -167,41 +345,109 @@ static void scan_file()
 
 static void do_tick()
 {
-	static char buffer[64*1024];
-	DEMOREC_CHUNK chunk;
+	static char compresseddata[MAX_SNAPSHOT_SIZE];
+	static char decompressed[MAX_SNAPSHOT_SIZE];
+	static char data[MAX_SNAPSHOT_SIZE];
+	int chunk_size, chunk_type, chunk_tick;
+	int data_size;
+	int got_snapshot = 0;
 
 	/* update ticks */
 	playbackinfo.previous_tick = playbackinfo.current_tick;
 	playbackinfo.current_tick = playbackinfo.next_tick;
+	chunk_tick = playbackinfo.current_tick;
 
 	while(1)
 	{
-		int r = read_chunk_header(&chunk);
-		if(chunk.size > sizeof(buffer))
-		{
-			dbg_msg("demorec/playback", "chunk is too big %d", chunk.size);
-			r = 1;
-		}
-		
-		if(r)
+		if(read_chunk_header(&chunk_type, &chunk_size, &chunk_tick))
 		{
 			/* stop on error or eof */
+			dbg_msg("demorec", "end of file");
 			demorec_playback_stop();
 			break;
 		}
 		
 		/* read the chunk */
-		io_read(play_file, buffer, chunk.size);
+		if(chunk_size)
+		{
+			if(io_read(play_file, compresseddata, chunk_size) != chunk_size)
+			{
+				/* stop on error or eof */
+				dbg_msg("demorec", "error reading chunk");
+				demorec_playback_stop();
+				break;
+			}
+			
+			data_size = netcommon_decompress(compresseddata, chunk_size, decompressed, sizeof(decompressed));
+			if(data_size < 0)
+			{
+				/* stop on error or eof */
+				dbg_msg("demorec", "error during network decompression");
+				demorec_playback_stop();
+				break;
+			}
 			
-		if(mem_comp(chunk.type, "TICK", 4) == 0)
+			data_size = intpack_decompress(decompressed, data_size, data);
+
+			if(data_size < 0)
+			{
+				dbg_msg("demorec", "error during intpack decompression");
+				demorec_playback_stop();
+				break;
+			}
+		}
+			
+		if(chunk_type == CHUNKTYPE_DELTA)
 		{
-			DEMOREC_TICKMARKER marker = *(DEMOREC_TICKMARKER *)buffer;
-			swap_endian(&marker.tick, sizeof(int), 1);
-			playbackinfo.next_tick = marker.tick;
-			break;
+			/* process delta snapshot */
+			static char newsnap[MAX_SNAPSHOT_SIZE];
+			
+			got_snapshot = 1;
+			
+			data_size = snapshot_unpack_delta((SNAPSHOT*)playback_lastsnapshotdata, (SNAPSHOT*)newsnap, data, data_size);
+			
+			if(data_size >= 0)
+			{
+				if(play_callback_snapshot)
+					play_callback_snapshot(newsnap, data_size);
+
+				playback_lastsnapshotdata_size = data_size;
+				mem_copy(playback_lastsnapshotdata, newsnap, data_size);
+			}
+			else
+				dbg_msg("demorec", "error duing unpacking of delta, err=%d", data_size);
+		}
+		else if(chunk_type == CHUNKTYPE_SNAPSHOT)
+		{
+			/* process full snapshot */
+			got_snapshot = 1;
+			
+			playback_lastsnapshotdata_size = data_size;
+			mem_copy(playback_lastsnapshotdata, data, data_size);
+			if(play_callback_snapshot)
+				play_callback_snapshot(data, data_size);
+		}
+		else
+		{
+			/* if there were no snapshots in this tick, replay the last one */
+			if(!got_snapshot && play_callback_snapshot && playback_lastsnapshotdata_size != -1)
+			{
+				got_snapshot = 1;
+				play_callback_snapshot(playback_lastsnapshotdata, playback_lastsnapshotdata_size);
+			}
+			
+			/* check the remaining types */
+			if(chunk_type&CHUNKTYPEFLAG_TICKMARKER)
+			{
+				playbackinfo.next_tick = chunk_tick;
+				break;
+			}
+			else if(chunk_type == CHUNKTYPE_MESSAGE)
+			{
+				if(play_callback_message)
+					play_callback_message(data, data_size);
+			}
 		}
-		else if(play_callback)
-			play_callback(chunk, buffer);
 	}
 }
 
@@ -238,6 +484,8 @@ int demorec_playback_load(const char *filename)
 	playbackinfo.current_tick = -1;
 	playbackinfo.previous_tick = -1;
 	playbackinfo.speed = 1;
+	
+	playback_lastsnapshotdata_size = -1;
 
 	/* read the header */	
 	io_read(play_file, &playbackinfo.header, sizeof(playbackinfo.header));
@@ -256,6 +504,12 @@ int demorec_playback_load(const char *filename)
 	return 0;
 }
 
+int demorec_playback_nextframe()
+{
+	do_tick();
+	return demorec_isplaying();
+}
+
 int demorec_playback_play()
 {
 	/* fill in previous and next tick */
@@ -326,7 +580,7 @@ int demorec_playback_update()
 			int64 curtick_start = (playbackinfo.current_tick)*freq/SERVER_TICK_SPEED;
 			int64 prevtick_start = (playbackinfo.previous_tick)*freq/SERVER_TICK_SPEED;
 			playbackinfo.intratick = (playbackinfo.current_time - prevtick_start) / (float)(curtick_start-prevtick_start);
-			playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (freq/(float)SERVER_TICK_SPEED);
+			playbackinfo.ticktime = (playbackinfo.current_time - prevtick_start) / (float)freq;
 		}
 		
 		if(playbackinfo.current_tick == playbackinfo.previous_tick ||
diff --git a/src/engine/e_demorec.h b/src/engine/e_demorec.h
index f6ceb382..bc54dddd 100644
--- a/src/engine/e_demorec.h
+++ b/src/engine/e_demorec.h
@@ -10,8 +10,8 @@ typedef struct DEMOREC_HEADER
 
 typedef struct DEMOREC_CHUNK
 {
-	char type[4];
-	int size;
+	char type[2];
+	unsigned short size;
 } DEMOREC_CHUNK;
 
 typedef struct DEMOREC_TICKMARKER
@@ -44,13 +44,15 @@ typedef struct DEMOREC_PLAYBACKINFO
 
 int demorec_record_start(const char *filename, const char *netversion, const char *map, int map_crc, const char *type);
 int demorec_isrecording();
-void demorec_record_write(const char *type, int size, const void *data);
+void demorec_record_snapshot(int tick, const void *data, int size);
+void demorec_record_message(const void *data, int size);
 int demorec_record_stop();
 
-typedef void (*DEMOREC_PLAYCALLBACK)(DEMOREC_CHUNK chunk, void *data);
+typedef void (*DEMOREC_PLAYCALLBACK)(void *data, int size);
 
-int demorec_playback_registercallback(DEMOREC_PLAYCALLBACK cb);
+int demorec_playback_registercallbacks(DEMOREC_PLAYCALLBACK snapshot_cb, DEMOREC_PLAYCALLBACK message_cb);
 int demorec_playback_load(const char *filename);
+int demorec_playback_nextframe();
 int demorec_playback_play();
 void demorec_playback_pause();
 void demorec_playback_unpause();
diff --git a/src/engine/e_network.c b/src/engine/e_network.c
index 2ed45707..770cfae1 100644
--- a/src/engine/e_network.c
+++ b/src/engine/e_network.c
@@ -96,6 +96,16 @@ void send_packet_connless(NETSOCKET socket, NETADDR *addr, const void *data, int
 	net_udp_send(socket, addr, buffer, 6+data_size);
 }
 
+int netcommon_compress(const void *data, int data_size, void *output, int output_size)
+{
+	return huffman_compress(&huffmanstate, data, data_size, output, output_size);
+}
+
+int netcommon_decompress(const void *data, int data_size, void *output, int output_size)
+{
+	return huffman_decompress(&huffmanstate, data, data_size, output, output_size);
+}
+
 void send_packet(NETSOCKET socket, NETADDR *addr, NETPACKETCONSTRUCT *packet)
 {
 	unsigned char buffer[NET_MAX_PACKETSIZE];
@@ -207,7 +217,6 @@ void netcommon_openlog(const char *filename)
 	datalog = io_open(filename, IOFLAG_WRITE);
 }
 
-
 static const unsigned freq_table[256+1] = {
 	1<<30,4545,2657,431,1950,919,444,482,2244,617,838,542,715,1814,304,240,754,212,647,186,
 	283,131,146,166,543,164,167,136,179,859,363,113,157,154,204,108,137,180,202,176,
diff --git a/src/engine/e_network.h b/src/engine/e_network.h
index 1ac02708..4064e8d6 100644
--- a/src/engine/e_network.h
+++ b/src/engine/e_network.h
@@ -57,6 +57,8 @@ typedef int (*NETFUNC_NEWCLIENT)(int cid, void *user);
 /* both */
 void netcommon_openlog(const char *filename);
 void netcommon_init();
+int netcommon_compress(const void *data, int data_size, void *output, int output_size);
+int netcommon_decompress(const void *data, int data_size, void *output, int output_size);
 
 /* server side */
 NETSERVER *netserver_open(NETADDR bindaddr, int max_clients, int flags);
diff --git a/src/engine/e_snapshot.c b/src/engine/e_snapshot.c
index 88949b65..cce8a06e 100644
--- a/src/engine/e_snapshot.c
+++ b/src/engine/e_snapshot.c
@@ -11,7 +11,6 @@ static short item_sizes[64] = {0};
 
 void snap_set_staticsize(int itemtype, int size)
 {
-	dbg_msg("","%d = %d", itemtype, size);
 	item_sizes[itemtype] = size;
 }
 
@@ -397,12 +396,12 @@ int snapshot_unpack_delta(SNAPSHOT *from, SNAPSHOT *to, void *srcdata, int data_
 		else
 		{
 			if(data+1 > end)
-				return -1;
+				return -2;
 			itemsize = (*data++) * 4;
 		}
 		snapshot_current = type;
 		
-		if(range_check(end, data, itemsize) || itemsize < 0) return -1;
+		if(range_check(end, data, itemsize) || itemsize < 0) return -3;
 		
 		key = (type<<16)|id;
 		
@@ -411,7 +410,7 @@ int snapshot_unpack_delta(SNAPSHOT *from, SNAPSHOT *to, void *srcdata, int data_
 		if(!newdata)
 			newdata = (int *)snapbuild_new_item(&builder, key>>16, key&0xffff, itemsize);
 
-		if(range_check(end, newdata, itemsize)) return -1;
+		/*if(range_check(end, newdata, itemsize)) return -4;*/
 			
 		fromindex = snapshot_get_item_index(from, key);
 		if(fromindex != -1)
diff --git a/src/engine/server/es_server.c b/src/engine/server/es_server.c
index 067d7a89..9c4d74c5 100644
--- a/src/engine/server/es_server.c
+++ b/src/engine/server/es_server.c
@@ -342,7 +342,7 @@ int server_send_msg(int client_id)
 	
 	/* write message to demo recorder */
 	if(!(info->flags&MSGFLAG_NORECORD))
-		demorec_record_write("MESG", info->size, info->data);
+		demorec_record_message(info->data, info->size);
 
 	if(!(info->flags&MSGFLAG_NOSEND))
 	{
@@ -379,20 +379,14 @@ static void server_do_snap()
 	{
 		char data[MAX_SNAPSHOT_SIZE];
 		int snapshot_size;
-		DEMOREC_TICKMARKER marker;
 
-		/* write tick marker */
-		marker.tick = server_tick();
-		swap_endian(&marker, sizeof(int), sizeof(marker)/sizeof(int));
-		demorec_record_write("TICK", sizeof(marker), &marker);
-		
 		/* build snap and possibly add some messages */
 		snapbuild_init(&builder);
 		mods_snap(-1);
 		snapshot_size = snapbuild_finish(&builder, data);
 		
 		/* write snapshot */
-		demorec_record_write("SNAP", snapshot_size, data);
+		demorec_record_snapshot(server_tick(), data, snapshot_size);
 	}
 
 	/* create snapshots for all clients */