// QuickLZ data compression library
// Copyright (C) 2006-2008 Lasse Mikkel Reinhold
// lar@quicklz.com
//
// QuickLZ can be used for free under the GPL-1 or GPL-2 license (where anything 
// released into public must be open source) or under a commercial license if such 
// has been acquired (see http://www.quicklz.com/order.html). The commercial license 
// does not cover derived or ported versions created by third parties under GPL.

#include "quicklz.h"

int qlz_get_setting(int setting)
{
	switch (setting)
	{
		case 0: return QLZ_COMPRESSION_LEVEL;
		case 1: return QLZ_SCRATCH_COMPRESS;
		case 2: return QLZ_SCRATCH_DECOMPRESS;
		case 3: return QLZ_STREAMING_MODE_VALUE;
#ifdef test_rle
		case 4: return 1;
#else
		case 4: return 0;
#endif
#ifdef speedup_incompressible
		case 5: return 1;
#else
		case 5: return 0;
#endif
#ifdef memory_safe
		case 6: return 1;
#else
		case 6: return 0;
#endif
		case 7: return QLZ_VERSION_MAJOR;
		case 8: return QLZ_VERSION_MINOR;
		case 9: return QLZ_VERSION_REVISION;
	}
	return -1;
}

static inline unsigned int hash_func(unsigned int i)
{
	return ((i >> 12) ^ i) & 0x0fff;
}

static inline unsigned int fast_read(void const *src, unsigned int bytes)
{
#ifndef X86X64
	unsigned char *p = (unsigned char*)src;
	switch (bytes)
	{
		case 4:
			return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24);
		case 3: 
			return(*p | *(p + 1) << 8 | *(p + 2) << 16);
		case 2:
			return(*p | *(p + 1) << 8);
		case 1: 
			return(*p);
	}
	return 0;
#else
	if (bytes >= 1 && bytes <= 4)
		return *((unsigned int*)src);
	else
		return 0;
#endif
}

static inline void fast_write(unsigned int f, void *dst, unsigned int bytes)
{
#ifndef X86X64
	unsigned char *p = (unsigned char*)dst;

	switch (bytes)
	{
		case 4: 
			*p = (unsigned char)f;
			*(p + 1) = (unsigned char)(f >> 8);
			*(p + 2) = (unsigned char)(f >> 16);
			*(p + 3) = (unsigned char)(f >> 24);
			return;
		case 3:
			*p = (unsigned char)f;
			*(p + 1) = (unsigned char)(f >> 8);
			*(p + 2) = (unsigned char)(f >> 16);
			return;
		case 2:
			*p = (unsigned char)f;
			*(p + 1) = (unsigned char)(f >> 8);
			return;
		case 1:
			*p = (unsigned char)f;
			return;
	}
#else
	switch (bytes)
	{
		case 4: 
			*((unsigned int*)dst) = f;
			return;
		case 3:
			*((unsigned int*)dst) = f;
			return;
		case 2:
#if QLZ_COMPRESSION_LEVEL == 0
// 2 byte writes are common in level 0
			*((unsigned short int*)dst) = (unsigned short int)f;
#else
			*((unsigned int*)dst) = f;
#endif
			return;
		case 1:
			*((unsigned char*)dst) = (unsigned char)f;
			return;
	}
#endif
}

static inline void memcpy_up(unsigned char *dst, const unsigned char *src, unsigned int n)
{
	// cannot be replaced by overlap handling of memmove() due to LZSS algorithm
#ifndef X86X64

	if(n > 8 && src + n < dst)
		memcpy(dst, src, n);
	else
	{
		unsigned char *end = dst + n;
		while(dst < end)
		{
			*dst = *src;
			dst++;
			src++;
		}
	}
#else
	if (n < 5)
		*((unsigned int*)dst) = *((unsigned int*)src);
	else
	{
		unsigned char *end = dst + n;
		while(dst < end)
		{
			*((unsigned int*)dst) = *((unsigned int*)src);
			dst = dst + 4;
			src = src + 4;
		}
	}
#endif
}

static inline unsigned int fast_read_safe(void const *src, unsigned int bytes, const unsigned char *invalid)
{
#ifdef memory_safe 
	if ((const unsigned char *)src + 4 > (const unsigned char *)invalid)
		return 0xffffffff;
#endif
	invalid = invalid;
	return fast_read(src, bytes);
}

unsigned int qlz_compress_core(const void *source, unsigned char *destination, unsigned int size,  const unsigned char *hashtable[][AND + 1], const unsigned char *first_valid, unsigned char *hash_counter)
{
	const unsigned char *source_c = (const unsigned char*)source;
	unsigned char *destination_c = (unsigned char*)destination;
	const unsigned char *last_byte = source_c + size - 1;
	const unsigned char *src = source_c;
	unsigned char *cword_ptr = destination_c;
	unsigned char *dst = destination_c + 4;
	unsigned int cword_val = 1U << 31;
	const unsigned char *guarantee_uncompressed = last_byte - 8;

#ifdef speedup_incompressible
		unsigned char *prev_dst = dst;
		const unsigned char *prev_src = src;
#endif

	hash_counter = hash_counter;
	first_valid = first_valid;

	// save first 4 bytes uncompressed
	while(src < source_c + 4 && src < guarantee_uncompressed)
	{
		cword_val = (cword_val >> 1);
		*dst = *src;
		dst++;
		src++;
	}
	
	while(src < guarantee_uncompressed)
	{
		unsigned int fetch;
		if ((cword_val & 1) == 1)
		{
			// check if destinationc pointer could exceed destination buffer
			if (dst > destination_c + size)
				return 0;
					
			// store control word
			fast_write((cword_val >> 1) | (1U << 31), cword_ptr, 4);
			cword_ptr = dst;
			dst += 4;
			cword_val = 1U << 31;

#ifdef speedup_incompressible
			// check if source chunk is compressible
			if (dst - prev_dst > src - prev_src && src > source_c + 1000)
			{
				int q;
				for(q = 0; q < 30 && src + 31 < guarantee_uncompressed && dst + 35 < destination_c + size; q++)
				{

#if(QLZ_COMPRESSION_LEVEL == 0)
					int w;
					for(w = 0; w < 31; w++)
					{
						fetch = fast_read(src + w, 4);
						*(unsigned int*)&hashtable[hash_func(fetch)][0] = fast_read(src + w, 4);
						hashtable[hash_func(fetch)][1] = src + w;
					}
#endif
					fast_write((1U << 31), dst - 4, 4);
					memcpy(dst, src, 31);

					dst += 4*8 - 1 + 4;
					src += 4*8 - 1;
					prev_src = src;
					prev_dst = dst;
					cword_ptr = dst - 4;
				}
			} 
#endif
		}
#ifdef test_rle
		// check for rle sequence
		if (fast_read(src, 4) == fast_read(src + 1, 4))
		{
			const unsigned char *orig_src; 
			fetch = fast_read(src, 4);
			orig_src = src;
			do src = src + 4; while (src <= guarantee_uncompressed - 4 && fetch == fast_read(src, 4));
			if((src - orig_src) <= 2047) 
			{			
				fast_write(((fetch & 0xff) << 16) | (unsigned int)((src - orig_src) << 4) | 15, dst, 4);
				dst = dst + 3;
			}
			else
			{
				fast_write(((fetch & 0xff) << 16) | 15, dst, 4);
				fast_write((unsigned int)(src - orig_src), dst + 3, 4);
				dst = dst + 7;
			}
			cword_val = (cword_val >> 1) | (1 << 31);
		}
		else
#endif
		{
			const unsigned char *o;
			unsigned int hash, matchlen;

#if(QLZ_COMPRESSION_LEVEL < 2)
			unsigned int cached;

			fetch = fast_read(src, 4);
			hash = hash_func(fetch);

			cached = fetch ^ *(unsigned int*)&hashtable[hash][0];
			*(unsigned int*)&hashtable[hash][0] = fetch;

			o = hashtable[hash][1];
			hashtable[hash][1] = src;

#else
			unsigned char c;
			unsigned int k, m;
			const unsigned char *offset2 = 0;
			
			fetch = fast_read(src, 4);
			hash = hash_func(fetch);

			matchlen = 0;
			c = hash_counter[hash];			
			for(k = 0; k < AND + 1; k++)
			{
				o = hashtable[hash][(c - k) & AND];
				if(o > first_valid && o < src - 3 && *(src + matchlen) == *(o + matchlen) && (fast_read(o, 3) & 0xffffff) == (fetch & 0xffffff) && src - o < 131071)
				{	
					size_t remaining;
					remaining = guarantee_uncompressed - src;
					m = 3;
					if (fast_read(o, 4) == fetch)
					{
						while(*(o + m) == *(src + m) && m < remaining)
							m++;
					}
					if (m > matchlen)
					{
						matchlen = m;
						offset2 = o;
					}
				}
			}
			o = offset2;			
			c = (hash_counter[hash] + 1) & AND;
			hash_counter[hash] = c;			
			hashtable[hash][c] = src;
#endif

#if(QLZ_COMPRESSION_LEVEL == 0)
			if (o != 0 && (cached & 0xffffff) == 0 && src - o > 3)
#elif(QLZ_COMPRESSION_LEVEL == 1)
			if ((cached & 0xffffff) == 0 && o > first_valid && o < src - 3 && ((fast_read(o, 3) ^ fast_read(src, 3)) & 0xffffff) == 0 && src - o < 131071)
#elif(QLZ_COMPRESSION_LEVEL > 1)
			if(matchlen == 3)
#endif
			{
				unsigned int offset;
				offset = (unsigned int)(src - o);
					
#if(QLZ_COMPRESSION_LEVEL < 2)
				if (cached & 0xffffffff)
#endif
				{
#if (QLZ_COMPRESSION_LEVEL > 2)
					unsigned int u;
					for(u = 1; u < 3; u++)
					{
						hash = hash_func(fast_read(src + u, 4));
						c = (hash_counter[hash] + 1) & AND;
						hash_counter[hash] = c;
						hashtable[hash][c] = src + u;
					}	
#endif

#if (QLZ_COMPRESSION_LEVEL == 0)					
					cword_val = (cword_val >> 1) | (1U << 31);
					fast_write(3 | (hash << 4), dst, 2);
					src += 3;
					dst += 2;
#else

					if(offset <= 63)
					{
						// encode lz match
						*dst = (unsigned char)(offset << 2);
						cword_val = (cword_val >> 1) | (1U << 31);
						src += 3;
						dst++;
					}
					else if (offset <= 16383)
					{
						// encode lz match
						unsigned int f = (offset << 2) | 1;
						fast_write(f, dst, 2);
						cword_val = (cword_val >> 1) | (1U << 31);
						src += 3;
						dst += 2;
					}
					else
					{
						// encode literal
						*dst = *src;
						src++;
						dst++;
						cword_val = (cword_val >> 1);
					}
#endif
				}		
#if(QLZ_COMPRESSION_LEVEL > 1)
			}								
			else if(matchlen > 3)
			{
#elif(QLZ_COMPRESSION_LEVEL < 2)
				else
#endif
				{
					// encode lz match
					unsigned int offset;
					
#if(QLZ_COMPRESSION_LEVEL < 2)
					const unsigned char *old_src = src;
					offset = (unsigned int)(src - o);
					cword_val = (cword_val >> 1) | (1U << 31);

					src += 3;
					while(*(o + (src - old_src)) == *src && src < guarantee_uncompressed)
						src++;
					matchlen = (unsigned int)(src - old_src);
#else
					unsigned int u;
					offset = (unsigned int)(src - o);
					cword_val = (cword_val >> 1) | (1U << 31);

#if (QLZ_COMPRESSION_LEVEL > 2)
					for(u = 1; u < matchlen; u++)
#else
					for(u = 1; u < matchlen && u < 5; u++)
#endif
					{
						hash = hash_func(fast_read(src + u, 4));
						c = (hash_counter[hash] + 1) & AND;
						hash_counter[hash] = c;
						hashtable[hash][c] = src + u;
					}
					src += matchlen;			
#endif					

#if (QLZ_COMPRESSION_LEVEL == 0)
					if (matchlen < 15)
					{
						fast_write(matchlen | (hash << 4), dst, 2);
						dst += 2;
					} 
					else if (matchlen < 255)
					{
						fast_write(hash << 4, dst, 2);
						*(dst + 2) = (unsigned char)matchlen;
						dst += 3;
					}
					else
					{
						fast_write(hash << 4, dst, 2);
						*(dst + 2) = 0;
						fast_write(matchlen, dst + 3, 4);
						dst += 7;
					}					
#else
					if (matchlen <= 18 && offset <= 1023)
					{
						unsigned int f = ((matchlen - 3) << 2) | (offset << 6) | 2;
						fast_write(f, dst, 2);
						dst += 2;
					}

					else if(matchlen <= 34 && offset <= 65535)
					{
						unsigned int f = ((matchlen - 3) << 3) | (offset << 8) | 3;
						fast_write(f, dst, 3);
						dst += 3;
					}
					else if (matchlen >= 3)
					{
						if (matchlen <= 2050)
						{
							unsigned int f = ((matchlen - 3) << 4) | (offset << 15) | 7;
							fast_write(f, dst, 4);
							dst += 4;
						}
						else
						{
							fast_write(7, dst, 4);
							fast_write(matchlen, dst + 4, 4);
							fast_write(offset, dst + 8, 4);
							dst += 12;							
						} 
					}
#endif
				}
			}
			
			else
			{
				// encode literal
				*dst = *src;
				src++;
				dst++;
				cword_val = (cword_val >> 1);
			}
		}
	}

// save last source bytes as literals
	while (src <= last_byte)
	{
		if ((cword_val & 1) == 1)
		{
			fast_write((cword_val >> 1) | (1U << 31), cword_ptr, 4);
			cword_ptr = dst;
			dst += 4;
			cword_val = 1U << 31;
		}

		if (src < last_byte - 2 && src > source_c + 3)
		{		
			hashtable[hash_func(fast_read(src, 4))][1] = src;
			*(unsigned int*)&hashtable[hash_func(fast_read(src, 4))][0] = fast_read(src, 4);
		}
		*dst = *src;
		src++;
		dst++;

		cword_val = (cword_val >> 1);
	}

	while((cword_val & 1) != 1)
		cword_val = (cword_val >> 1);

	fast_write((cword_val >> 1) | (1U << 31), cword_ptr, 4);

	// min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument
	if (dst - destination_c < 9)
		return 9;
	else
		return (unsigned int)(dst - destination_c);
}

size_t qlz_decompress_core(const unsigned char *source, void *destination, size_t size, size_t source_size, unsigned char *first_valid, const unsigned char *hashtable[])
{
	const unsigned char *source_c = (const unsigned char*)source;
	unsigned char *destination_c = (unsigned char*)destination;
	const unsigned char *src = source_c;
	unsigned char *dst = destination_c;
	const unsigned char* last_byte_successor = destination_c + size;
	unsigned int cword_val = 1;
	const unsigned int bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
	const unsigned char *guaranteed_uncompressed = last_byte_successor - 4;
	unsigned char *last_hashed = destination_c + 3;

	first_valid = first_valid;
	last_hashed = last_hashed;
	hashtable = hashtable;
		
	// prevent spurious memory read on a source with size < 4
	if (dst >= guaranteed_uncompressed)
	{
		src += 4;
		while(dst < last_byte_successor)
		{
			*dst = *src;
			dst++;
			src++;
		}

		return (unsigned int)(dst - destination_c);
	}


	for(;;) 
	{
		unsigned int fetch;

		if (cword_val == 1)
		{
			// fetch control word
			cword_val = fast_read_safe(src, 4, source_c + source_size) | (1U << 31);
			src += 4;
		}
		
		fetch = fast_read_safe(src, 4, source_c + source_size);

	 	// check if we must decode lz match
		if ((cword_val & 1) == 1)
		{
			unsigned int matchlen;

#if(QLZ_COMPRESSION_LEVEL == 0)
			unsigned int hash;
			const unsigned char *offset2;

			cword_val = cword_val >> 1;			

			if((fetch & 0xf) != 15)
			{
				hash = (fetch >> 4) & 0xfff;
				offset2 = hashtable[hash];

				if((fetch & 0xf) != 0)
				{
					matchlen = (fetch & 0xf);
					src += 2;			
				}
				else if((fetch & 0x00ff0000) != 0)
				{
					matchlen = *(src + 2);
					src += 3;							
				}
				else
				{
					matchlen = fast_read(src + 3, 4);
					src += 7;
				}
				memcpy_up(dst, offset2, matchlen);
				while(last_hashed < dst)
				{
					last_hashed++;
					hashtable[hash_func(fast_read(last_hashed, 4))] = last_hashed;
				}				
				dst += matchlen;
				last_hashed = dst - 1;				
			}
				
#else		
			unsigned int offset;
			cword_val = cword_val >> 1;			

			if ((fetch & 3) == 0)
			{
				offset = (fetch & 0xff) >> 2;
#ifdef memory_safe	
				if (3 > (unsigned int)(guaranteed_uncompressed - dst) || offset > (unsigned int)(dst - first_valid))
					return 0;
#endif
				memcpy_up(dst, dst - offset, 3);
				dst += 3;
				src++;
			}
			else if ((fetch & 2) == 0)
			{
				offset = (fetch & 0xffff) >> 2;
#ifdef memory_safe	
				if (3 > (unsigned int)(guaranteed_uncompressed - dst) || offset > (unsigned int)(dst - first_valid)) 
					return 0;
#endif
				memcpy_up(dst, dst - offset, 3);
				dst += 3;
				src += 2;
			}
			else if ((fetch & 1) == 0)
			{
				offset = (fetch & 0xffff) >> 6;
				matchlen = ((fetch >> 2) & 15) + 3;
#ifdef memory_safe	
				if (matchlen > (unsigned int)(guaranteed_uncompressed - dst) || offset > (unsigned int)(dst - first_valid)) 
					return 0;
#endif
				memcpy_up(dst, dst - offset, matchlen);
				src += 2;
				dst += matchlen;
			}
			else if ((fetch & 4) == 0)
			{
				offset = (fetch & 0xffffff) >> 8;
				matchlen = ((fetch >> 3) & 31) + 3;
#ifdef memory_safe	
				if (matchlen > (unsigned int)(guaranteed_uncompressed - dst) || offset > (unsigned int)(dst - first_valid)) 
					return 0;
#endif
				memcpy_up(dst, dst - offset, matchlen);
				src += 3;
				dst += matchlen;
			}
			else if ((fetch & 8) == 0)
			{
				offset = (fetch >> 15);
				if (offset != 0)
				{
					matchlen = ((fetch >> 4) & 2047) + 3;
					src += 4;
				}
				else
				{
					matchlen = fast_read_safe(src + 4, 4, source_c + source_size);
					offset = fast_read_safe(src + 8, 4, source_c + source_size);
					src += 12;
				}
#ifdef memory_safe	
				if (matchlen > (unsigned int)(guaranteed_uncompressed - dst) || offset > (unsigned int)(dst - first_valid)) 
					return 0;
#endif
				memcpy_up(dst, dst - offset, matchlen);
				dst += matchlen;
			}
#endif
			else
			{
				// decode rle sequence
				unsigned char rle_char;
				rle_char = (unsigned char)(fetch >> 16);
				matchlen = ((fetch >> 4) & 0xfff);

				if(matchlen != 0)
					src += 3;
				else
				{
					matchlen = fast_read_safe(src + 3, 4, source_c + source_size);
					src += 7;
				}

#ifdef memory_safe	
				if(matchlen > (unsigned int)(guaranteed_uncompressed - dst)) 
					return 0;
#endif
				memset(dst, rle_char, matchlen);

#if(QLZ_COMPRESSION_LEVEL == 0)
				while(last_hashed < dst - 1)
				{
					last_hashed++;
					hashtable[hash_func(fast_read(last_hashed, 4))] = last_hashed;
				}				
				last_hashed = dst - 1 + matchlen;
#endif
				dst += matchlen; 
			}
		}
		else
		{
			// decode literal
#ifdef memory_safe 
			if (4 > destination_c + size - dst || src > source_c + source_size + 4) 
				return 0;
#endif
			memcpy_up(dst, src, 4);

			dst += bitlut[cword_val & 0xf];
			src += bitlut[cword_val & 0xf];
			cword_val = cword_val >> (bitlut[cword_val & 0xf]);

#if(QLZ_COMPRESSION_LEVEL == 0)
			while(last_hashed < dst - 3)
			{
				last_hashed++;
				hashtable[hash_func(fast_read(last_hashed, 4))] = last_hashed;
			}
#endif
			if (dst >= guaranteed_uncompressed)
			{
				// decode last literals and exit				
				while(dst < last_byte_successor)
				{
					if (cword_val == 1)
					{
						src += 4;
						cword_val = 1U << 31;
					}
					if (1 > destination_c + size - dst)
						return 0;

					*dst = *src;
					dst++;
					src++;
					cword_val = cword_val >> 1;
				}

#if(QLZ_COMPRESSION_LEVEL == 0)
				while(last_hashed < last_byte_successor - 4)
				{
					last_hashed++;
					hashtable[hash_func(fast_read(last_hashed, 4))] = last_hashed;
				}
#endif
				if((src - 1) - source_c > 8) // 8 bytes comp. size excessive len is ok
					return 0;
				else if(dst - destination_c - size == 0)
					return size;
				else
					return 0;
			}
		}
	}
}

size_t qlz_size_decompressed(const char *source)
{
	unsigned int n, r;
	n = (((*source) & 2) == 2) ? 4 : 1;
	r = fast_read(source + 1 + n, n);
	r = r & (0xffffffff >> ((4 - n)*8));
	return r;
}

size_t qlz_size_compressed(const char *source)
{
	unsigned int n, r;
	n = (((*source) & 2) == 2) ? 4 : 1;
	r = fast_read(source + 1, n);
	r = r & (0xffffffff >> ((4 - n)*8));
	return r;
}

size_t qlz_compress(const void *source, char *destination, size_t size, char *scratch)
{
	// 1-8 bytes for aligning (not 0-7!); 8 bytes for buffersize (padds on 32 bit cpu); QLZ_HASH_SIZE hash table; QLZ_STREAMING_MODE_ROUNDED bytes streambuffer; optional QLZ_HASH_ENTRIES byte hash counter
	unsigned char *buffer_aligned = (unsigned char *)scratch + 8 - (((size_t)scratch) % 8);
	const unsigned char *(*hashtable)[AND + 1] = (const unsigned char *(*)[AND + 1])(buffer_aligned + 8);
	size_t *buffersize = (size_t *)buffer_aligned;
	unsigned char *streambuffer = buffer_aligned + 8 + QLZ_HASH_SIZE;
	unsigned int r;
	unsigned int compressed, base;
	unsigned char *hash_counter = streambuffer + QLZ_STREAMING_MODE_ROUNDED;

	if(size == 0 || size > 0xffffffff)
		return 0;

#if (QLZ_COMPRESSION_LEVEL == 0 && QLZ_STREAMING_MODE_ROUNDED == 0)
		memset((void *)hashtable, 0, QLZ_HASH_SIZE);
#endif

	if(size < 216)
		base = 3;
	else
		base = 9;

// if not QLZ_STREAMING_MODE, then QLZ_STREAMING_MODE_ROUNDED == 0 and first case (streaming buffer full) is executed unconditionally, functioning as block comp.
	if (*buffersize + size - 1 >= QLZ_STREAMING_MODE_ROUNDED)
	{
#if (QLZ_COMPRESSION_LEVEL == 0 && QLZ_STREAMING_MODE_ROUNDED != 0)
		memset((void *)hashtable, 0, QLZ_HASH_SIZE);
#endif

		r = base + qlz_compress_core(source, (unsigned char*)destination + base, (unsigned int)size, hashtable, (const unsigned char*)source, hash_counter);
#if (QLZ_COMPRESSION_LEVEL == 0 && QLZ_STREAMING_MODE_ROUNDED != 0)
		memset((void *)hashtable, 0, QLZ_HASH_SIZE);
#endif

		if(r == base)
		{
			memcpy(destination + base, source, size);
			r = (unsigned int)size + base;
			compressed = 0;
		}
		else
			compressed = 1;
		*buffersize = 0;
	}
	else
	{
		memcpy(streambuffer + *buffersize, source, size);
		r = base + qlz_compress_core(streambuffer + *buffersize, (unsigned char*)destination + base, (unsigned int)size, hashtable, streambuffer, hash_counter);

		if(r == base)
		{
			memcpy(destination + base, streambuffer + *buffersize, size);
			r = (unsigned int)size + base;
			compressed = 0;
				
			memset((void*)hashtable, 0, QLZ_HASH_SIZE);
		}
		else
			compressed = 1;
		*buffersize += size;
	}
	
	if(base == 3)
	{
		*destination = (unsigned char)(0 | compressed);
		*(destination + 1) = (unsigned char)r;
		*(destination + 2) = (unsigned char)size;
	}
	else
	{
		*destination = (unsigned char)(2 | compressed);
		fast_write(r, destination + 1, 4);
		fast_write((unsigned int)size, destination + 5, 4);
	}

#if (QLZ_COMPRESSION_LEVEL == 0)
		*destination = (*destination) | 4;
#endif 

	return (size_t)r;
}



size_t qlz_decompress(const char *source, void *destination, char *scratch)
{
	// 1-8 bytes for aligning (not 0-7!); 8 bytes for buffersize (padds on 32bit cpu); QLZ_STREAMING_MODE_ROUNDED streambuffer; QLZ_HASH_SIZE hash table
	unsigned char *buffer_aligned = (unsigned char *)scratch + 8 - (((size_t)scratch) % 8);
	size_t *buffersize = (size_t *)buffer_aligned;
	unsigned int headerlen = 2*((((*source) & 2) == 2) ? 4 : 1) + 1; // get header len

	unsigned char *streambuffer = buffer_aligned + 8;
	const unsigned char **hashtable = (const unsigned char **)(streambuffer + QLZ_STREAMING_MODE_ROUNDED);

	size_t dsiz = qlz_size_decompressed((char *)source);
	size_t csiz = qlz_size_compressed((char *)source);
	if (*buffersize + qlz_size_decompressed((char *)source) - 1 >= QLZ_STREAMING_MODE_ROUNDED) 
	{	
		if((*source & 1) == 1)		
			qlz_decompress_core((const unsigned char *)source + headerlen, destination, dsiz, csiz, (unsigned char*)destination, hashtable);
		else
			memcpy(destination, source + headerlen, dsiz);
		*buffersize = 0;
	}
	else
	{
		if((*source & 1) == 1)
			qlz_decompress_core((const unsigned char *)source + headerlen, streambuffer + *buffersize, dsiz, csiz, streambuffer, hashtable);
		else		
			memcpy(streambuffer + *buffersize, source + headerlen, dsiz);
		memcpy(destination, streambuffer + *buffersize, dsiz);
		*buffersize += dsiz;
	}
	return dsiz;
}
