#include <windows.h>
#include "..\\dtr.hpp"

class BBStream
{
private:
	BYTE* base;
	BYTE* p;
	int   idx;
	int   len;
	bool  eof;
	WORD  bits;
public:
	BBStream(BYTE* from, int blockSize)
	{
		base = p = from;

		len = blockSize;
		idx = 0;
		eof = false;

		bits  = getByte();
		bits += 256*getByte();
	}

	BYTE getByte(void)
	{
		if(p - base == len) { eof = true; return 0; }
		return *p++;
	}

	BYTE getBit()
	{
		WORD mask[]  = { 0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };

		BYTE bit = (bits & mask[idx]) ? 1 : 0;
		if(idx == 15)
		{
			bits  = getByte();
			bits += 256*getByte();
		}

		idx = (idx + 1) % 16;
		return bit;
	}

	BYTE getBits(int n)
	{
		BYTE r = 0;
		do { r = 2*r + getBit(); } while(--n);
		return r;
	}

	bool error(void) { return eof; }
};

bool WINAPI extract(HoHdr *hdr, const BYTE *source, DWORD, BYTE *unpackBuffer, DWORD *noUnpackedBytes, DWORD)
{
	BYTE *from = (BYTE*)source;
	BYTE *to   = unpackBuffer;

	if(source[0] != 'H' || source[1] != 'R')
		from += 0x103;

	BBStream s(from + 12, from[4] + 256*from[5]);

	*to++ = s.getByte();

	BYTE noBits = 2;
	BYTE mask[] = { 0, 0, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0 };

	while(!s.error())
	{
		while(s.getBit()) *to++ = s.getByte();

		WORD len = 0;
		BYTE bb  = 0;
		do
		{
			bb = s.getBits(2);
			len += bb;
		} while(bb == 0x03 && len != 0x0f);

		short offset = 0;

		if(len == 0)
		{
			offset = 0xfff8 + s.getBits(3);
			*to++ = to[offset];
			continue;
		}

		if(len == 1)
		{
			BYTE code = s.getBits(2);

			if(code == 2)
			{
				BYTE b = s.getByte();
				if(b >= 0xe0)
				{
					b <<= 1; ++b; // rlca
					b ^= 2;       // xor c

					if(b == 0xff) { ++noBits; continue; }

					offset = 0xff00 + b - 0x0f;

					*to++ = to[offset];
					*to++ = s.getByte();
					*to++ = to[offset];
					continue;
				}
				offset = 0xff00 + b;
			}

			if(code == 0 || code == 1)
			{
				offset = s.getByte();
				offset += 256*(code ? 0xfe : 0xfd);
			}
			if(code == 3) offset = 0xffe0 + s.getBits(5);

			for(BYTE i = 0; i < 2; ++i)
				*to++ = to[offset];
			continue;
		}

		if(len == 3)
		{
			if(s.getBit())
			{
				offset = 0xfff0 + s.getBits(4);
				*to++ = to[offset];
				*to++ = s.getByte();
				*to++ = to[offset];
				continue;
			}

			if(s.getBit())
			{
				BYTE noBytes = 6 + s.getBits(4);
				for(BYTE i = 0; i < 2*noBytes; ++i)
					*to++ = s.getByte();
				continue;
			}

			len = s.getBits(7);
			if(len == 0x0f) break; // EOF
			if(len <  0x0f) len = 256*len + s.getByte();
		}

		if(len == 2) ++len;

		BYTE code = s.getBits(2);

		if(code == 1)
		{
			BYTE b = s.getByte();

			if(b >= 0xe0)
			{
				if(len > 3) return false;

				b <<= 1; ++b; // rlca
				b ^= 3;       // xor c

				offset = 0xff00 + b - 0x0f;

				*to++ = to[offset];
				*to++ = s.getByte();
				*to++ = to[offset];
				continue;
			}
			offset = 0xff00 + b;
		}

		if(code == 0) offset = 0xfe00 + s.getByte();
		if(code == 2) offset = 0xffe0 + s.getBits(5);
		if(code == 3)
		{
			offset  = 256*(mask[noBits] + s.getBits(noBits));
			offset += s.getByte();
		}

		for(WORD i = 0; i < len; ++i)
			*to++ = to[offset];
	}

	for(int i = 0; i < 6; ++i)
		*to++ = from[6+i];

	*noUnpackedBytes = to-unpackBuffer;

	return true;
}

bool WINAPI detect(DetectorResult &result, const char*, const HoHdr*, const BYTE *data, int file_size)
{
	WORD size;
	WORD packed_size;

	while(true)
	{
		if(data[0] == 'H' && data[1] == 'R')
		{
			size        = *(WORD*)(data + 2);
			packed_size = *(WORD*)(data + 4);

			// check for HRiP
			if(data[2] == 'i' && (data[2+5] == 0 || data[2+5] == 1))
				return false;

			break;
		}

		if(data[0x103] == 'H' && data[0x104] == 'R')
		{
			size        = *(WORD*)(data + 0x105);
			packed_size = *(WORD*)(data + 0x107);

			break;
		}

		return false;
	}

	if(packed_size > size || packed_size > file_size)
		return false;

	result.action = do_extract;
	lstrcpy(result.title, "[hrust 1.x]");

	return true;
}
