#define MODULE_LOG_PREFIX "emu"

#include "globals.h"

#ifdef WITH_EMU

#include "cscrypt/des.h"
#include "module-streamrelay.h"
#include "module-emulator-osemu.h"
#include "module-emulator-powervu.h"
#include "oscam-string.h"
#include "oscam-time.h"

static inline uint8_t get_bit(uint8_t byte, uint8_t bitnb)
{
	return ((byte & (1 << bitnb)) ? 1 : 0);
}

static inline uint8_t set_bit(uint8_t val, uint8_t bitnb, uint8_t biton)
{
	return (biton ? (val | (1 << bitnb)) : (val & ~(1 << bitnb)));
}

static uint8_t crc8_calc(uint8_t *data, int len)
{
	int i;
	uint8_t crc = 0;
	uint8_t crcTable[256] =
	{
		0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
		0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
		0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
		0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
		0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
		0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
		0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
		0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
		0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
		0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
		0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
		0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
		0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
		0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
		0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
		0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
	};

	for (i = 0; i < len; i++)
	{
		crc = crcTable[data[i] ^ crc];
	}

	return crc;
}

static void pad_data(uint8_t *data, int len, uint8_t *dataPadded)
{
	int i;
	uint8_t pad[] =
	{
		0x01, 0x02, 0x22, 0x04, 0x20, 0x2A, 0x1F, 0x03,
		0x04, 0x06, 0x02, 0x0C, 0x2B, 0x2B, 0x01, 0x7B
	};

	for (i = 0; i < len; i++)
	{
		dataPadded[i] = data[i];
	}

	dataPadded[len] = 0x01;

	for (i = len + 1; i < 0x2F; i++)
	{
		dataPadded[i] = 0x00;
	}

	dataPadded[0x2F] = len;

	for (i = 0; i < 16; i++)
	{
		dataPadded[0x30 + i] = pad[i];
	}
}

static void hash_mode_01_custom_md5(uint8_t *data, uint8_t *hash)
{
	int i, j, s;
	uint32_t a, b, c, d, f = 0, g;

	uint32_t T[] =
	{
		0x783E16F6, 0xC267AC13, 0xA2B17F12, 0x6B8A31A4,
		0xF910654D, 0xB702DBCB, 0x266CEF60, 0x5145E47C,
		0xB92E00D6, 0xE80A4A64, 0x8A07FA77, 0xBA7D89A9,
		0xEBED8022, 0x653AAF2B, 0xF118B03B, 0x6CC16544,
		0x96EB6583, 0xF4E27E35, 0x1ABB119E, 0x068D3EF2,
		0xDAEAA8A5, 0x3C312A3D, 0x59538388, 0xA100772F,
		0xAB0165CE, 0x979959E7, 0x5DD8F53D, 0x189662BA,
		0xFD021A9C, 0x6BC2D338, 0x1EFF667E, 0x40C66888,
		0x6E9F07FF, 0x0CEF442F, 0x82D20190, 0x4E8CAEAC,
		0x0F7CB305, 0x2E73FBE7, 0x1CE884A2, 0x7A60BD52,
		0xC348B30D, 0x081CE3AA, 0xA12220E7, 0x38C7EC79,
		0xCBD8DD3A, 0x62B4FBA5, 0xAD2A63DB, 0xE4D0852E,
		0x53DE980F, 0x9C8DDA59, 0xA6B4CEDE, 0xB48A7692,
		0x0E2C46A4, 0xEB9367CB, 0x165D72EE, 0x75532B45,
		0xB9CA8E97, 0x08C8837B, 0x966F917B, 0x527515B4,
		0xF27A5E5D, 0xB71E6267, 0x7603D7E6, 0x9837DD69
	}; // CUSTOM T

	uint8_t r[] =
	{
		0x06, 0x0A, 0x0F, 0x15, 0x05, 0x09, 0x0E, 0x14,
		0x04, 0x0B, 0x10, 0x17, 0x07, 0x0C, 0x11, 0x16
	}; // STANDARD REORDERED

	uint8_t tIdxInit[] = { 0, 1, 5, 0 }; // STANDARD
	uint8_t tIdxIncr[] = { 1, 5, 3, 7 }; // STANDARD

	uint32_t h[] = { 0xEAD81D2E, 0xCE4DC6E9, 0xF9B5C301, 0x10325476 }; // CUSTOM h0, h1, h2, STANDARD h3
	uint32_t dataLongs[16];

	for (i = 0; i < 16; i++)
	{
		dataLongs[i] = (data[4 * i + 0] << 0) + (data[4 * i + 1] << 8) +
						(data[4 * i + 2] << 16) + (data[4 * i + 3] << 24);
	}

	a = h[0];
	b = h[1];
	c = h[2];
	d = h[3];

	for (i = 0; i < 4; i++)
	{
		g = tIdxInit[i];

		for (j = 0; j < 16; j++)
		{
			if (i == 0)
			{
				f = (b & c) | (~b & d);
			}
			else if (i == 1)
			{
				f = (b & d) | (~d & c);
			}
			else if (i == 2)
			{
				f = (b ^ c ^ d);
			}
			else if (i == 3)
			{
				f = (~d | b) ^ c;
			}

			f = dataLongs[g] + a + T[16 * i + j] + f;

			s = r[4 * i + (j & 3)];
			f = (f << s) | (f >> (32 - s));

			a = d;
			d = c;
			c = b;
			b += f;

			g = (g + tIdxIncr[i]) & 0xF;
		}
	}

	h[0] += a;
	h[1] += b;
	h[2] += c;
	h[3] += d;

	for (i = 0; i < 4; i++)
	{
		hash[4 * i + 0] = h[i] >> 0;
		hash[4 * i + 1] = h[i] >> 8;
		hash[4 * i + 2] = h[i] >> 16;
		hash[4 * i + 3] = h[i] >> 24;
	}
}

static void hash_mode_02(uint8_t *data, uint8_t *hash)
{
	int i;
	uint32_t a, b, c, d, e, f = 0, tmp;
	uint32_t h[] = { 0x81887F3A, 0x36CCA480, 0x99056FB1, 0x79705BAE };
	uint32_t dataLongs[80];

	for (i = 0; i < 16; i++)
	{
		dataLongs[i] = (data[4 * i + 0] << 24) + (data[4 * i + 1] << 16) +
						(data[4 * i + 2] << 8) + (data[4 * i + 3] << 0);
	}

	for (i = 0; i < 64; i++)
	{
		dataLongs[16 + i] = dataLongs[16 + i - 2];
		dataLongs[16 + i] ^= dataLongs[16 + i - 7];
		dataLongs[16 + i] ^= dataLongs[16 + i - 13];
		dataLongs[16 + i] ^= dataLongs[16 + i - 16];
	}

	a = dataLongs[0];
	b = dataLongs[1];
	c = dataLongs[2];
	d = dataLongs[3];
	e = dataLongs[4];

	for (i = 0; i < 80; i++)
	{
		if (i < 0x15) f = (b & c) | (~b & d);
		else if (i < 0x28) f = (b ^ c ^ d);
		else if (i < 0x3D) f = (b & c) | (c & d) | (b & d);
		else if (i < 0x50) f = (b ^ c ^ d);

		tmp = a;
		a = e + f + (a << 5) + (a >> 27) + h[i / 0x14] + dataLongs[i];
		e = d;
		d = c;
		c = (b << 30) + (b >> 2);
		b = tmp;
	}

	dataLongs[0] += a;
	dataLongs[1] += b;
	dataLongs[2] += c;
	dataLongs[3] += d;

	for (i = 0; i < 4; i++)
	{
		hash[4 * i + 0] = dataLongs[i] >> 24;
		hash[4 * i + 1] = dataLongs[i] >> 16;
		hash[4 * i + 2] = dataLongs[i] >> 8;
		hash[4 * i + 3] = dataLongs[i] >> 0;
	}
}

static void hash_mode_03(uint8_t *data, uint8_t *hash)
{
	int i, j, k, s, s2, tmp;
	uint32_t a, b, c, d, f = 0, g;
	uint32_t a2, b2, c2, d2, f2 = 0, g2;

	uint32_t T[] = { 0xC88F3F2E, 0x967506BA, 0xDA877A7B, 0x0DECCDFE };
	uint32_t T2[] = { 0x01F42668, 0x39C7CDA5, 0xD490E2FE, 0x9965235D };

	uint8_t r[] =
	{
		0x0B, 0x0E, 0x0F, 0x0C, 0x05, 0x08, 0x07, 0x09,
		0x0B, 0x0D, 0x0E, 0x0F, 0x06, 0x07, 0x09, 0x08,
		0x07, 0x06, 0x08, 0x0D, 0x0B, 0x09, 0x07, 0x0F,
		0x07, 0x0C, 0x0F, 0x09, 0x0B, 0x07, 0x0D, 0x0C
	};

	uint8_t tIdxIncr[] =
	{
		0x07, 0x04, 0x0D, 0x01, 0x0A, 0x06, 0x0F, 0x03,
		0x0C, 0x00, 0x09, 0x05, 0x02, 0x0E, 0x0B, 0x08,
		0x05, 0x0D, 0x02, 0x00, 0x04, 0x09, 0x03, 0x08,
		0x01, 0x0A, 0x07, 0x0B, 0x06, 0x0F, 0x0C, 0x0E
	};

	uint32_t h[] = { 0xC8616857, 0x9D3F5B8E, 0x4D7B8F76, 0x97BC8D80 };

	uint32_t dataLongs[80];
	uint32_t result[4];

	for (i = 0; i < 16; i++)
	{
		dataLongs[i] = (data[4 * i + 0] << 24) + (data[4 * i + 1] << 16) +
						(data[4 * i + 2] << 8) + (data[4 * i + 3] << 0);
	}

	a = h[0];
	b = h[1];
	c = h[2];
	d = h[3];

	a2 = h[3];
	b2 = h[2];
	c2 = h[1];
	d2 = h[0];

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 16; j++)
		{
			tmp = j;

			for (k = 0; k < i; k++)
			{
				tmp = tIdxIncr[tmp];
			}

			g = 0x0F - tmp;
			g2 = tmp;

			if (i == 0) f = (b & d) | (~d & c);
			else if (i == 1) f = (~c | b) ^ d;
			else if (i == 2) f = (~b & d) | (b & c);
			else if (i == 3) f = (b ^ c ^ d);

			if (i == 0) f2 = (b2 ^ c2 ^ d2);
			else if (i == 1) f2 = (~b2 & d2) | (b2 & c2);
			else if (i == 2) f2 = (~c2 | b2) ^ d2;
			else if (i == 3) f2 = (b2 & d2) | (~d2 & c2);

			f = dataLongs[g] + a + T[i] + f;
			s = r[0x0F + (((i & 1) ^ 1) << 4) - j];
			f = (f << s) | (f >> (32 - s));

			f2 = dataLongs[g2] + a2 + T2[i] + f2;
			s2 = r[((i & 1) << 4) + j];
			f2 = (f2 << s2) | (f2 >> (32 - s2));

			a = d;
			d = (c << 10) | (c >> 22);
			c = b;
			b = f;

			a2 = d2;
			d2 = (c2 << 10) | (c2 >> 22);
			c2 = b2;
			b2 = f2;
		}
	}

	result[0] = h[3] + b + a2;
	result[1] = h[2] + c + b2;
	result[2] = h[1] + d + c2;
	result[3] = h[0] + a + d2;

	for (i = 0; i < 4; i++)
	{
		hash[4 * i + 0] = result[i] >> 0;
		hash[4 * i + 1] = result[i] >> 8;
		hash[4 * i + 2] = result[i] >> 16;
		hash[4 * i + 3] = result[i] >> 24;
	}
}

static const uint8_t table04[] =
{
	0x02, 0x03, 0x07, 0x0B, 0x0D, 0x08, 0x00, 0x01, 0x2B, 0x2D, 0x28, 0x20, 0x21, 0x0A, 0x0C, 0x0E,
	0x22, 0x36, 0x23, 0x27, 0x29, 0x24, 0x25, 0x26, 0x2A, 0x3C, 0x3E, 0x3F, 0x0F, 0x2C, 0x2E, 0x2F,
	0x12, 0x13, 0x17, 0x1B, 0x1C, 0x18, 0x10, 0x11, 0x19, 0x14, 0x15, 0x16, 0x1A, 0x09, 0x04, 0x05,
	0x32, 0x33, 0x37, 0x3B, 0x06, 0x1C, 0x1E, 0x1F, 0x3D, 0x38, 0x30, 0x31, 0x39, 0x34, 0x35, 0x3A
};

static const uint8_t table05[] =
{
	0x08, 0x09, 0x0A, 0x03, 0x04, 0x3F, 0x27, 0x28, 0x29, 0x2A, 0x05, 0x0B, 0x1B, 0x1C, 0x1C, 0x1E,
	0x20, 0x0C, 0x0D, 0x22, 0x23, 0x24, 0x00, 0x01, 0x02, 0x06, 0x07, 0x25, 0x26, 0x0E, 0x0F, 0x21,
	0x10, 0x11, 0x12, 0x2E, 0x2F, 0x13, 0x14, 0x15, 0x2B, 0x2C, 0x2D, 0x16, 0x17, 0x18, 0x19, 0x1A,
	0x30, 0x31, 0x37, 0x3B, 0x3C, 0x3D, 0x3E, 0x1F, 0x38, 0x39, 0x32, 0x33, 0x34, 0x35, 0x36, 0x3A
};

static const uint8_t table06[] =
{
	0x00, 0x01, 0x02, 0x06, 0x07, 0x08, 0x03, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x04, 0x05, 0x09, 0x0D,
	0x20, 0x21, 0x22, 0x26, 0x27, 0x3A, 0x3B, 0x3C, 0x3E, 0x3F, 0x10, 0x11, 0x12, 0x16, 0x17, 0x28,
	0x18, 0x13, 0x14, 0x15, 0x19, 0x1C, 0x1A, 0x1B, 0x1C, 0x1E, 0x1F, 0x23, 0x24, 0x25, 0x29, 0x2D,
	0x30, 0x31, 0x32, 0x36, 0x37, 0x38, 0x33, 0x34, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x35, 0x39, 0x3D
};

static const uint8_t table07[] =
{
	0x10, 0x11, 0x12, 0x17, 0x1C, 0x1E, 0x0E, 0x38, 0x39, 0x3A, 0x13, 0x14, 0x29, 0x2A, 0x16, 0x1F,
	0x00, 0x01, 0x02, 0x3C, 0x3D, 0x3E, 0x3F, 0x07, 0x08, 0x09, 0x03, 0x04, 0x05, 0x06, 0x3B, 0x0A,
	0x20, 0x21, 0x22, 0x19, 0x1A, 0x1B, 0x1C, 0x0B, 0x0C, 0x15, 0x23, 0x24, 0x25, 0x26, 0x18, 0x0F,
	0x30, 0x31, 0x2B, 0x33, 0x34, 0x35, 0x36, 0x37, 0x27, 0x28, 0x2C, 0x2D, 0x2E, 0x2F, 0x32, 0x0D
};

static const uint8_t table08[] =
{
	0x10, 0x11, 0x1E, 0x17, 0x18, 0x19, 0x12, 0x13, 0x14, 0x1C, 0x1C, 0x15, 0x0D, 0x05, 0x06, 0x0A,
	0x00, 0x01, 0x0E, 0x07, 0x08, 0x09, 0x02, 0x2D,	0x25, 0x26, 0x2A, 0x2B, 0x2F, 0x03, 0x04, 0x0C,
	0x20, 0x21, 0x2E, 0x27, 0x28, 0x29, 0x30, 0x31,	0x3E, 0x37, 0x38, 0x39, 0x22, 0x23, 0x24, 0x2C,
	0x32, 0x33, 0x34, 0x3C, 0x3D, 0x35, 0x36, 0x3A, 0x3B, 0x0B, 0x0F, 0x16, 0x1A, 0x1B, 0x1F, 0x3F
};

static const uint8_t table09[] =
{
	0x20, 0x21, 0x24, 0x22, 0x23, 0x2A, 0x2B, 0x33, 0x35, 0x38, 0x39, 0x36, 0x2D, 0x2C, 0x2E, 0x2F,
	0x00, 0x01, 0x04, 0x02, 0x25, 0x28, 0x08, 0x09, 0x06, 0x07, 0x0A, 0x0B, 0x0D, 0x0C, 0x0E, 0x0F,
	0x10, 0x11, 0x14, 0x12, 0x13, 0x15, 0x19, 0x16, 0x29, 0x26, 0x03, 0x17, 0x1A, 0x1C, 0x1C, 0x1E,
	0x30, 0x31, 0x34, 0x32, 0x37, 0x3A, 0x3B, 0x3D, 0x3C, 0x3E, 0x3F, 0x1B, 0x05, 0x18, 0x27, 0x1F
};

static const uint8_t table0A[] =
{
	0x00, 0x04, 0x05, 0x0B, 0x0C, 0x06, 0x09, 0x0A, 0x0E, 0x0D, 0x0F, 0x25, 0x15, 0x1B, 0x1C, 0x16,
	0x10, 0x11, 0x01, 0x02, 0x03, 0x07, 0x08, 0x12, 0x13, 0x17, 0x18, 0x14, 0x23, 0x27, 0x28, 0x24,
	0x30, 0x31, 0x32, 0x33, 0x37, 0x38, 0x34, 0x35, 0x3B, 0x3C, 0x20, 0x21, 0x22, 0x2B, 0x2C, 0x26,
	0x36, 0x39, 0x3A, 0x3E, 0x3D, 0x19, 0x1A, 0x1E, 0x1C, 0x1F, 0x3F, 0x29, 0x2A, 0x2E, 0x2D, 0x2F
};

static void hash_modes_04_to_0A_tables(uint8_t *data, uint8_t *hash, const uint8_t *table)
{
	int i;

	for (i = 0; i < 16; i++)
	{
		hash[i] = table[i];
		hash[i] ^= data[table[i]];
		hash[i] ^= table[16 + i];
		hash[i] ^= data[table[16 + i]];
		hash[i] ^= table[32 + i];
		hash[i] ^= data[table[32 + i]];
		hash[i] ^= table[48 + i];
		hash[i] ^= data[table[48 + i]];
	}
}

static const uint8_t table0F[] = { 0xC7, 0x45, 0x15, 0x71, 0x61, 0x07, 0x05, 0x47 };
static const uint8_t table10[] = { 0x0F, 0x47, 0x2B, 0x6C, 0xAD, 0x0F, 0xB3, 0xEA };
static const uint8_t table11[] = { 0xB1, 0x46, 0xD1, 0x66, 0x5D, 0x28, 0x59, 0xD2 };
static const uint8_t table12[] = { 0x0B, 0x4B, 0xD7, 0x68, 0x5F, 0xAD, 0x4B, 0xBB };
static const uint8_t table13[] = { 0x4F, 0x4E, 0xE1, 0x6A, 0x21, 0xD3, 0xF7, 0xA6 };
static const uint8_t table14[] = { 0xDD, 0x39, 0xB9, 0x65, 0x03, 0x91, 0xF1, 0xAC };
static const uint8_t table15[] = { 0x3F, 0x50, 0xB5, 0x6F, 0x37, 0xC9, 0x13, 0x5D };
static const uint8_t table16[] = { 0xF9, 0x5C, 0xFD, 0x72, 0x19, 0x42, 0x23, 0x6B };
static const uint8_t table17[] = { 0xDF, 0x60, 0x93, 0x64, 0x33, 0x16, 0xB3, 0x8A };
static const uint8_t table18[] = { 0x09, 0x64, 0x5F, 0x6B, 0xFB, 0x21, 0x19, 0xE4 };

static void hash_modes_0F_to_18_tables(uint8_t *data, uint8_t *hash, const uint8_t *table)
{
	int i;
	uint32_t t[4], tmp;

	memset(hash, 0x00, 16);

	t[0] = (table[1] << 8) + table[0];
	t[1] = (table[3] << 8) + table[2];
	t[2] = (table[5] << 8) + table[4];
	t[3] = (table[7] << 8) + table[6];

	for (i = 0; i < 60; i += 4)
	{
		t[0] = ((t[0] & 0xFFFF) * t[2]) + (t[0] >> 16);
		t[1] = ((t[1] & 0xFFFF) * t[3]) + (t[1] >> 16);
		tmp = t[0] + t[1];

		hash[(i + 0) & 0x0F] = hash[(i + 0) & 0x0F] ^ data[i + 0] ^ (tmp >> 24);
		hash[(i + 1) & 0x0F] = hash[(i + 1) & 0x0F] ^ data[i + 1] ^ (tmp >> 16);
		hash[(i + 2) & 0x0F] = hash[(i + 2) & 0x0F] ^ data[i + 2] ^ (tmp >> 8);
		hash[(i + 3) & 0x0F] = hash[(i + 3) & 0x0F] ^ data[i + 3] ^ (tmp >> 0);
	}
}

static const uint8_t table19[] = { 0x02, 0x03, 0x05, 0x10 };
static const uint8_t table1A[] = { 0x01, 0x05, 0x08, 0x10 };
static const uint8_t table1B[] = { 0x03, 0x07, 0x08, 0x10 };
static const uint8_t table1C[] = { 0x03, 0x05, 0x0A, 0x10 };
static const uint8_t table1D[] = { 0x03, 0x07, 0x0A, 0x10 };
static const uint8_t table1E[] = { 0x01, 0x05, 0x0B, 0x10 };
static const uint8_t table1F[] = { 0x06, 0x07, 0x0B, 0x10 };
static const uint8_t table20[] = { 0x01, 0x08, 0x0B, 0x10 };
static const uint8_t table21[] = { 0x01, 0x07, 0x0C, 0x10 };
static const uint8_t table22[] = { 0x05, 0x0B, 0x0C, 0x10 };
static const uint8_t table23[] = { 0x0B, 0x0C, 0x0D, 0x10 };
static const uint8_t table24[] = { 0x07, 0x09, 0x0E, 0x10 };
static const uint8_t table25[] = { 0x01, 0x04, 0x0F, 0x10 };
static const uint8_t table26[] = { 0x07, 0x08, 0x0F, 0x10 };
static const uint8_t table27[] = { 0x02, 0x0B, 0x0F, 0x10 };

static void hash_modes_19_to_27_tables_3(uint8_t *data, uint8_t *hash, const uint8_t *table)
{
	int i;
	uint8_t val, it[4];
	uint16_t seed = 0xFFFF, tmp;

	memset(hash, 0x00, 16);

	for (i = 0; i < 4; i++)
	{
		it[i] = 0x10 - table[i];
	}

	for (i = 0; i < 16; i++)
	{
		val = ((seed >> it[0]) ^ (seed >> it[1]) ^ (seed >> it[2]) ^ (seed >> it[3])) & 0x01;

		if (val == 0x00)
		{
			seed = seed >> 1;
		}
		else
		{
			seed = (seed >> 1) | 0x8000;
		}
		tmp = seed + (data[i] << 8) + data[i + 32];

		val = ((seed >> it[0]) ^ (seed >> it[1]) ^ (seed >> it[2]) ^ (seed >> it[3])) & 0x01;

		if (val == 0x00)
		{
			seed = seed >> 1;
		}
		else
		{
			seed = (seed >> 1) | 0x8000;
		}
		tmp = tmp + seed + (data[i + 16] << 8) + data[i + 48];

		hash[i & 0x0F] ^= tmp >> 8;
		hash[(i + 1) & 0x0F] ^= tmp;
	}
}

static void create_hash(uint8_t *data, int len, uint8_t *hash, int mode)
{
	if ((mode > 0x27) || (mode == 0x0B) || (mode == 0x0C) ||
		(mode == 0x0D) || (mode == 0x0E) || (mode == 0))
	{
		memset(hash, 0, 16);
		return;
	}

	uint8_t dataPadded[64];

	pad_data(data, len, dataPadded);

	switch (mode)
	{
		case 1:
			hash_mode_01_custom_md5(dataPadded, hash);
			break;

		case 2:
			hash_mode_02(dataPadded, hash);
			break;

		case 3:
			hash_mode_03(dataPadded, hash);
			break;

		case 4:
			hash_modes_04_to_0A_tables(dataPadded, hash, table04);
			break;

		case 5:
			hash_modes_04_to_0A_tables(dataPadded, hash, table05);
			break;

		case 6:
			hash_modes_04_to_0A_tables(dataPadded, hash, table06);
			break;

		case 7:
			hash_modes_04_to_0A_tables(dataPadded, hash, table07);
			break;

		case 8:
			hash_modes_04_to_0A_tables(dataPadded, hash, table08);
			break;

		case 9:
			hash_modes_04_to_0A_tables(dataPadded, hash, table09);
			break;

		case 10:
			hash_modes_04_to_0A_tables(dataPadded, hash, table0A);
			break;

		case 15:
			hash_modes_0F_to_18_tables(dataPadded, hash, table0F);
			break;

		case 16:
			hash_modes_0F_to_18_tables(dataPadded, hash, table10);
			break;

		case 17:
			hash_modes_0F_to_18_tables(dataPadded, hash, table11);
			break;

		case 18:
			hash_modes_0F_to_18_tables(dataPadded, hash, table12);
			break;

		case 19:
			hash_modes_0F_to_18_tables(dataPadded, hash, table13);
			break;

		case 20:
			hash_modes_0F_to_18_tables(dataPadded, hash, table14);
			break;

		case 21:
			hash_modes_0F_to_18_tables(dataPadded, hash, table15);
			break;

		case 22:
			hash_modes_0F_to_18_tables(dataPadded, hash, table16);
			break;

		case 23:
			hash_modes_0F_to_18_tables(dataPadded, hash, table17);
			break;

		case 24:
			hash_modes_0F_to_18_tables(dataPadded, hash, table18);
			break;

		case 25:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table19);
			break;

		case 26:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1A);
			break;

		case 27:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1B);
			break;

		case 28:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1C);
			break;

		case 29:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1D);
			break;

		case 30:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1E);
			break;

		case 31:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table1F);
			break;

		case 32:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table20);
			break;

		case 33:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table21);
			break;

		case 34:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table22);
			break;

		case 35:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table23);
			break;

		case 36:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table24);
			break;

		case 37:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table25);
			break;

		case 38:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table26);
			break;

		case 39:
			hash_modes_19_to_27_tables_3(dataPadded, hash, table27);
			break;

		default:
			cs_log("A new hash mode [%d] is in use.", mode);
			break;
	}
}

static void create_hash_mode_03(uint8_t *data, uint8_t *hash)
{
	int i, j, c;
	uint8_t buffer0[16], buffer1[8], buffer2[8], tmpBuff1[4], tmpBuff2[4];

	uint8_t table[] =
	{
		0x68, 0xCE, 0xE7, 0x71, 0xCC, 0x3A, 0x0B, 0x6E, 0x2A, 0x43, 0x17, 0x07, 0x5A, 0xD9, 0x14, 0x5B,
		0xB0, 0x8E, 0xA8, 0x7F, 0xD8, 0xA2, 0xCF, 0x73, 0xC2, 0xB9, 0x5D, 0x46, 0xDD, 0x2C, 0xE2, 0x2D,
		0xFD, 0x50, 0xE9, 0x7C, 0x28, 0x72, 0x9B, 0xAA, 0xEC, 0x24, 0x74, 0xAB, 0x00, 0x1C, 0x8B, 0x65,
		0x38, 0x13, 0x22, 0x82, 0xAC, 0x9A, 0x4D, 0x2B, 0xEA, 0x04, 0x31, 0x84, 0x32, 0x3D, 0x36, 0x53,
		0x5F, 0x42, 0x96, 0xDE, 0x47, 0x08, 0x51, 0x4B, 0x3E, 0xD1, 0x1E, 0x12, 0xD2, 0x1F, 0x7D, 0x26,
		0xCD, 0x57, 0x8C, 0xB6, 0xD3, 0xF8, 0x11, 0xAD, 0x6A, 0x88, 0x95, 0x21, 0xE8, 0xBF, 0x6B, 0x27,
		0xBE, 0xA3, 0x33, 0xB8, 0x9E, 0xB3, 0x6C, 0xC3, 0x06, 0xC7, 0x6F, 0x99, 0x97, 0xDA, 0x09, 0xAF,
		0xAE, 0xCB, 0x79, 0x37, 0x55, 0x85, 0x8D, 0x2F, 0x8A, 0x70, 0xA1, 0x7A, 0x66, 0x29, 0x67, 0x0F,
		0xEB, 0x9C, 0xC8, 0xC4, 0xD6, 0x4C, 0xDF, 0x1A, 0xC0, 0x01, 0x64, 0xBC, 0x4E, 0xE1, 0x54, 0xD7,
		0x4F, 0xB7, 0x5E, 0xCA, 0xF0, 0x91, 0xE4, 0x59, 0x4A, 0xC6, 0x83, 0x8F, 0xBD, 0x61, 0xFF, 0x56,
		0x92, 0xF1, 0x5C, 0x77, 0xC9, 0x20, 0xF4, 0xE5, 0x10, 0x69, 0x03, 0x1D, 0xD5, 0x45, 0xF6, 0x0E,
		0xEF, 0xA0, 0xE3, 0x58, 0xFC, 0xED, 0x80, 0x16, 0xEE, 0xFA, 0x02, 0xF5, 0xB4, 0x0A, 0xE0, 0x0C,
		0xF7, 0xF9, 0xBA, 0x7E, 0x18, 0x78, 0x19, 0xB5, 0x0D, 0x44, 0x34, 0xD4, 0xDC, 0x30, 0x6D, 0x3B,
		0x63, 0x41, 0x48, 0x40, 0xA7, 0xA5, 0xC5, 0x98, 0x76, 0x3F, 0xC1, 0x25, 0x93, 0x49, 0xD0, 0x62,
		0x2E, 0x75, 0xDB, 0x94, 0xF3, 0x52, 0x05, 0x81, 0xFB, 0xBB, 0xA6, 0x89, 0x39, 0xA4, 0xF2, 0xA9,
		0xFE, 0x60, 0x3C, 0x15, 0xB1, 0x35, 0x86, 0x9D, 0x9F, 0x90, 0x1B, 0xE6, 0x7B, 0x23, 0x87, 0xB2
	};

	for (i = 0; i < 4; i++)
	{
		buffer0[0 + i] = data[12 + i];
		buffer0[4 + i] = data[8 + i];
		buffer0[8 + i] = data[4 + i];
		buffer0[12 + i] = data[0 + i];
	}

	for (c = 0; c < 12; c++)
	{
		for (i = 0; i < 4; i++)
		{
			buffer1[0 + i] = buffer0[8 + i] ^ buffer0[12 + i];
			buffer1[4 + i] = buffer0[0 + i] ^ buffer0[4 + i];
		}

		for (i = 0; i < 8; i++)
		{
			buffer1[i] = table[buffer1[i] ^ data[16 + 16 * (c % 3) + i]];
		}

		for (j = 0; j < 8; j++)
		{
			buffer2[j] = 0;
			for (i = 0; i < 8; i++)
			{
				buffer2[j] ^= buffer1[i] * (j * i + 1);
			}
		}

		for (i = 0; i < 8; i++)
		{
			buffer2[i] = table[buffer2[i] ^ data[24 + 16 * (c % 3) + i]] ^ data[16 + 16 * (c % 3) + i];
		}

		for (i = 0; i < 4; i++)
		{
			buffer0[12 + i] ^= buffer2[0 + i];
			buffer0[8 + i] ^= buffer2[0 + i];
			buffer0[4 + i] ^= buffer2[4 + i];
			buffer0[0 + i] ^= buffer2[4 + i];
		}

		tmpBuff1[0] = buffer0[14];
		tmpBuff1[1] = buffer0[15];
		tmpBuff1[2] = buffer0[12] ^ buffer0[14];
		tmpBuff1[3] = buffer0[13] ^ buffer0[15];

		tmpBuff2[0] = buffer0[6];
		tmpBuff2[1] = buffer0[7];
		tmpBuff2[2] = buffer0[4] ^ buffer0[6];
		tmpBuff2[3] = buffer0[5] ^ buffer0[7];

		for (i = 0; i < 4; i++)
		{
			buffer0[12 + i] = tmpBuff1[i];
			buffer0[4 + i] = tmpBuff2[i];
		}
	}

	for (i = 0; i < 4; i++)
	{
		hash[0 + i] = buffer0[12 + i] ^ data[0 + i];
		hash[4 + i] = buffer0[8 + i] ^ data[4 + i];
		hash[8 + i] = buffer0[4 + i] ^ data[8 + i];
		hash[12 + i] = buffer0[0 + i] ^ data[12 + i];
	}
}

static void create_data_cw_mode_03(uint8_t *seed, int lenSeed, uint8_t *basecw,
									uint8_t val, uint8_t *ecmBody, uint8_t *data)
{
	int idxData = 8, idxSeed = 0, idxBase = 0;
	uint8_t padding[] =
	{
		0x4A, 0x56, 0x7F, 0x16, 0xFC, 0x1F, 0x5B, 0x95,
		0x19, 0xEF, 0x75, 0x14, 0x0E, 0x9E, 0x17, 0x3C,
		0xF5, 0xB7, 0xA0, 0x93, 0xA3, 0x0F, 0xFA, 0x38,
		0x7A, 0x34, 0x6C, 0xDC, 0xFB, 0xB0, 0x24, 0x42,
		0x74, 0x72, 0x1C, 0xDC, 0x1E, 0xA1, 0x6D, 0xAB,
		0xC8, 0x44, 0x53, 0xEF, 0x56, 0x00, 0xE9, 0x97,
		0x48, 0x77, 0xF8, 0x00, 0x8E, 0x0B, 0x78, 0xA2
	};

	memcpy(data + 8, padding, 56);

	data[0] = ecmBody[0x0F];
	data[1] = ecmBody[0x09];
	data[2] = ecmBody[0x10];
	data[3] = ecmBody[0x11];
	data[4] = ecmBody[0x05];
	data[5] = ecmBody[0x07];
	data[6] = ecmBody[0x08];
	data[7] = ecmBody[0x0A];

	while (idxBase < 7)
	{
		if ((idxBase == 0) || (idxBase == 2) || (idxBase == 5))
		{
			data[idxData++] = val;
		}

		if (idxSeed < lenSeed)
		{
			data[idxData++] = seed[idxSeed++];
		}

		data[idxData++] = basecw[idxBase++];
	}
}

static void create_data_unmask_mode_03(uint8_t *ecmBody, uint8_t *data)
{
	uint8_t padding[] =
	{
		0xB1, 0x7C, 0xD2, 0xA7, 0x5E, 0x45, 0x6C, 0x36,
		0xF0, 0xB6, 0x81, 0xF3, 0x25, 0x06, 0x65, 0x06,
		0x6B, 0xBF, 0x4C, 0xE7, 0xED, 0x6E, 0x85, 0x00,
		0xCC, 0xF2, 0x61, 0x48, 0x62, 0x24, 0x0E, 0x3C,
		0x05, 0x89, 0xA5, 0x39, 0x5A, 0x4E, 0x9B, 0xC8,
		0x14, 0x78, 0xEA, 0xB6, 0xFB, 0xF8, 0x10, 0xE6,
		0x61, 0xF5, 0x3A, 0xBC, 0x5B, 0x79, 0x09, 0x97
	};

	memcpy(data + 8, padding, 56);

	data[0] = ecmBody[0x17];
	data[1] = ecmBody[0x26];
	data[2] = ecmBody[0x19];
	data[3] = ecmBody[0x21];
	data[4] = ecmBody[0x26];
	data[5] = ecmBody[0x31];
	data[6] = ecmBody[0x21];
	data[7] = ecmBody[0x27];
}

static void hash_04_add(uint32_t *buffer, int a, int b, int c, int d, int e, int f)
{
	uint32_t tmp1 = (buffer[a] & 1) + (buffer[b] & 1);
	uint32_t tmp2 = (buffer[a] >> 1) + (buffer[b] >> 1) + (tmp1 >> 1);

	buffer[e] = buffer[c] + buffer[d] + (tmp2 >> 31);
	buffer[f] = tmp2 + tmp2 + (tmp1 & 1);
}

static void hash_04_shift(uint32_t *buffer, int a, int b, uint8_t shift)
{
	uint32_t tmp1 = (buffer[a] >> (32 - shift)) + (buffer[b] << shift);
	uint32_t tmp2 = (buffer[b] >> (32 - shift)) + (buffer[a] << shift);

	buffer[b] = tmp1;
	buffer[a] = tmp2;
}

static void hash_04_xor(uint32_t *buffer, int a, int b, int c, int d)
{
	buffer[a] ^= buffer[b];
	buffer[c] ^= buffer[d];
}

static void hash_04_swap(uint32_t *buffer, int a, int b)
{
	uint32_t tmp = buffer[a];

	buffer[a] = buffer[b];
	buffer[b] = tmp;
}

static void hash_04_core(uint32_t *buffer)
{
	hash_04_add(buffer, 0, 6, 7, 1, 7, 6);
	hash_04_shift(buffer, 5, 4, 0x0D);
	hash_04_xor(buffer, 4, 2, 5, 3);
	hash_04_swap(buffer, 7, 6);
	hash_04_add(buffer, 6, 2, 3, 7, 3, 2);
	hash_04_shift(buffer, 1, 0, 0x10);
	hash_04_xor(buffer, 0, 4, 1, 5);
	hash_04_add(buffer, 6, 2, 3, 7, 7, 6);
	hash_04_shift(buffer, 1, 0, 0x15);
	hash_04_add(buffer, 6, 0, 1, 7, 1, 0);
	hash_04_xor(buffer, 2, 4, 3, 5);
	hash_04_shift(buffer, 5, 4, 0x11);
	hash_04_xor(buffer, 4, 2, 5, 3);
	hash_04_swap(buffer, 3, 2);
}

static void create_hash_mode_04(uint8_t *data, uint8_t *hash)
{
	int i, j;
	uint32_t d0, d1, h0, h1, h2, h3;
	uint32_t buffer[] =
	{
		0x1F253724, 0x3E8136B3, 0x9677CEDF, 0x25B5E75A,
		0x9494BC16, 0xCFD3FB34, 0xF37C75BB, 0x97D4632E
	};

	for (j = 0; j < 64; j += 8)
	{
		d0 = (data[j + 3] << 24) + (data[j + 2] << 16) + (data[j + 1] << 8) + data[j + 0];
		d1 = (data[j + 7] << 24) + (data[j + 6] << 16) + (data[j + 5] << 8) + data[j + 4];

		buffer[0] ^= d0;
		buffer[1] ^= d1;

		for (i = 0; i < 2; i++)
		{
			hash_04_core(buffer);
		}

		buffer[6] ^= d0;
		buffer[7] ^= d1;
	}

	buffer[1] ^= 0x40000000;
	buffer[0] ^= 0x00000000;

	for (i = 0; i < 2; i++)
	{
		hash_04_core(buffer);
	}

	buffer[7] ^= 0x40000000;
	buffer[6] ^= 0x00000000;
	buffer[2] ^= 0xEE;

	for (i = 0; i < 4; i++)
	{
		hash_04_core(buffer);
	}

	h0 = buffer[0] ^ buffer[2] ^ buffer[4] ^ buffer[6];
	h1 = buffer[1] ^ buffer[3] ^ buffer[5] ^ buffer[7];

	hash[0] = (uint8_t)  h0;
	hash[1] = (uint8_t) (h0 >>  8);
	hash[2] = (uint8_t) (h0 >> 16);
	hash[3] = (uint8_t) (h0 >> 24);
	hash[4] = (uint8_t)  h1;
	hash[5] = (uint8_t) (h1 >>  8);
	hash[6] = (uint8_t) (h1 >> 16);
	hash[7] = (uint8_t) (h1 >> 24);

	buffer[4] ^= 0xDD;

	for (i = 0; i < 4; i++)
	{
		hash_04_core(buffer);
	}

	h2 = buffer[0] ^ buffer[2] ^ buffer[4] ^ buffer[6];
	h3 = buffer[1] ^ buffer[3] ^ buffer[5] ^ buffer[7];

	hash[8]  = (uint8_t)  h2;
	hash[9]  = (uint8_t) (h2 >> 8);
	hash[10] = (uint8_t) (h2 >> 16);
	hash[11] = (uint8_t) (h2 >> 24);
	hash[12] = (uint8_t)  h3;
	hash[13] = (uint8_t) (h3 >> 8);
	hash[14] = (uint8_t) (h3 >> 16);
	hash[15] = (uint8_t) (h3 >> 24);
}

static void create_data_cw_mode_04(uint8_t *seed, int lenSeed, uint8_t *basecw,
									uint8_t val, uint8_t *ecmBody, uint8_t *data)
{
	uint8_t padding[] =
	{
		0x18, 0xD6, 0x24, 0xA8, 0xDE, 0x14, 0xD8, 0x30,
		0x3C, 0xB2, 0x24, 0x54, 0x17, 0x5A, 0x28, 0x61,
		0xBC, 0xB9, 0x29, 0xAD, 0xA5, 0x13, 0xD4, 0x24,
		0x6D, 0x61, 0x40, 0xC8, 0xFD, 0x27, 0xD7, 0xFF,
		0x3E, 0x84, 0x50, 0xC2, 0x47, 0x4C, 0xD5, 0xC5,
		0xF2, 0x79, 0xAD, 0x02, 0xC5, 0x05, 0x7B, 0xFD,
		0x60, 0x4A, 0x16, 0xE5, 0xAA, 0x0E, 0x97, 0x1C
	};

	memcpy(data + 8, padding, 56);

	data[0] = ecmBody[0x0E];
	data[1] = ecmBody[0x0A];
	data[2] = ecmBody[0x0C];
	data[3] = ecmBody[0x04];
	data[4] = ecmBody[0x10];
	data[5] = ecmBody[0x08];
	data[6] = ecmBody[0x05];
	data[7] = ecmBody[0x0F];

	int idxData = 8, idxSeed = 0, idxBase = 0;

	while (idxBase < 7)
	{
		if ((idxBase == 0) || (idxBase == 1) || (idxBase == 2))
		{
			data[idxData++] = val;
		}

		if (idxSeed < lenSeed)
		{
			data[idxData++] = seed[idxSeed++];
		}

		data[idxData++] = basecw[idxBase++];
	}
}

static void create_data_unmask_mode_04(uint8_t *ecmBody, uint8_t *data)
{
	uint8_t padding[] =
	{
		0x0E, 0x4A, 0x85, 0x85, 0xF9, 0xC0, 0xCC, 0x00,
		0xBA, 0x9B, 0x98, 0x35, 0x4C, 0xD2, 0xC1, 0x6C,
		0x87, 0x32, 0x9B, 0x82, 0x31, 0x5B, 0x1D, 0xB4,
		0xB8, 0x98, 0x74, 0xFF, 0x31, 0x66, 0x08, 0x79,
		0x47, 0xCE, 0x96, 0x4D, 0xE9, 0x52, 0xCF, 0x8F,
		0xEC, 0x5C, 0x07, 0xBC, 0x09, 0xA2, 0x82, 0x78,
		0x3D, 0xB9, 0xFF, 0x3F, 0x76, 0x72, 0x6F, 0x9C
	};

	memcpy(data + 8, padding, 56);

	data[0] = ecmBody[0x17];
	data[1] = ecmBody[0x2B];
	data[2] = ecmBody[0x1D];
	data[3] = ecmBody[0x2D];
	data[4] = ecmBody[0x0B];
	data[5] = ecmBody[0x06];
	data[6] = ecmBody[0x2F];
	data[7] = ecmBody[0x1E];
}

static uint8_t get_mode_cw(uint8_t *extraData)
{
	uint64_t data = ((uint32_t)extraData[0] << 24) + (extraData[1] << 16) + (extraData[2] << 8) + extraData[3];
	uint64_t t1 = (data * 0x76E9DEA7) >> 50;
	uint64_t t2 = (t1 * 0x51EB851F) >> 36;
	uint64_t t3 = t2 * 0x32;
	uint8_t r = t1 - t3;
	return r;
}

static uint8_t get_mode_unmask(uint8_t *extraData)
{
	uint64_t data = ((uint32_t)extraData[0] << 24) + (extraData[1] << 16) + (extraData[2] << 8) + extraData[3];
	uint64_t t1 = (data * 0xB9CD6BE5) >> 45;
	uint64_t t2 = (t1 * 0x51EB851F) >> 36;
	uint64_t t3 = t2 * 0x32;
	uint8_t r = t1 - t3;
	return r;
}

static void create_data_ecm_emm(uint8_t *emmEcm, uint8_t *pos, int lenHeader, int len, uint8_t *data)
{
	int i;

	for (i = 0; i < len; i++)
	{
		data[i] = emmEcm[lenHeader + pos[i]];
	}
}

static uint8_t create_data_cw(uint8_t *seed, uint8_t lenSeed, uint8_t *baseCw,
								uint8_t val, uint8_t *seedEcmCw, uint8_t *data)
{
	int i;

	for (i = 0; i < lenSeed; i++)
	{
		data[i] = seed[i];
	}

	for (i = 0; i < 7; i++)
	{
		data[lenSeed + i] = baseCw[i];
	}

	data[lenSeed + 7] = val;

	for (i = 0; i < 16; i++)
	{
		data[lenSeed + 7 + 1 + i] = seedEcmCw[i];
	}

	return lenSeed + 7 + 1 + 0x10;
}

static uint8_t unmask_ecm(uint8_t *ecm, uint8_t *seedEcmCw, uint8_t *modeCW)
{
	int i, l;
	uint8_t data[64], mask[16];
	uint8_t hashModeEcm, hashModeCw, modeUnmask = 0;
	uint32_t crc;

	uint8_t sourcePos[] =
	{
		0x04, 0x05, 0x06, 0x07, 0x0A, 0x0B, 0x0C, 0x0D,
		0x0E, 0x0F, 0x10, 0x17, 0x1C, 0x1D, 0x1F, 0x23,
		0x24, 0x25, 0x26, 0x27, 0x29, 0x2C, 0x2D, 0x2E
	};

	uint8_t destPos[] =
	{
		0x08, 0x09, 0x11, 0x18, 0x19, 0x1A, 0x1B, 0x1E,
		0x20, 0x21, 0x22, 0x28, 0x2A, 0x2B, 0x2F, 0x30
	};

	uint8_t seedCwPos[] = { 0x07, 0x0A, 0x04, 0x0D, 0x05, 0x0E, 0x06, 0x0B, 0x10, 0x0C, 0x0F };

	// Create seed for CW decryption
	memset(seedEcmCw, 0, 16);

	int extraBytesLen = ecm[9];
	int startOffset = extraBytesLen + 10;

	for (i = 0; i < 11; i++)
	{
		seedEcmCw[i] = ecm[startOffset + seedCwPos[i]];
	}

	*modeCW = 0;
	if (extraBytesLen > 0)
	{
		*modeCW = get_mode_cw(ecm + 10);
	}

	// Read hash mode CW
	hashModeCw = ecm[28 + extraBytesLen] ^ crc8_calc(seedEcmCw, 16);

	// Create mask for ECM decryption
	create_data_ecm_emm(ecm, sourcePos, startOffset, 24, data);

	hashModeEcm = ecm[8] ^ crc8_calc(data, 24);

	if (extraBytesLen > 0)
	{
		modeUnmask = get_mode_unmask(ecm + 10);
	}

	if (modeUnmask == 0x03)
	{
		ecm[startOffset + 0x21] -= ecm[startOffset + 0x07];
		ecm[startOffset + 0x26] -= ecm[startOffset + 0x05];
		ecm[startOffset + 0x26] -= ecm[startOffset + 0x08];
		ecm[startOffset + 0x19] -= ecm[startOffset + 0x06];
		ecm[startOffset + 0x31] -= ecm[startOffset + 0x09];
		ecm[startOffset + 0x27] -= ecm[startOffset + 0x0C];
		ecm[startOffset + 0x21] -= ecm[startOffset + 0x0B];
		ecm[startOffset + 0x17] -= ecm[startOffset + 0x04];

		create_data_unmask_mode_03(ecm + startOffset, data);
		create_hash_mode_03(data, mask);

		// Unmask body
		ecm[startOffset + 0x06] ^= mask[0x02];
		ecm[startOffset + 0x0B] ^= mask[0x06];
		ecm[startOffset + 0x0C] ^= mask[0x07];
		ecm[startOffset + 0x0D] ^= mask[0x08];
		ecm[startOffset + 0x0E] ^= mask[0x09];
		ecm[startOffset + 0x0F] ^= mask[0x0A];
		ecm[startOffset + 0x11] ^= mask[0x0B];
		ecm[startOffset + 0x18] ^= mask[0x0C];
		ecm[startOffset + 0x2D] ^= mask[0x0A];
		ecm[startOffset + 0x07] ^= mask[0x03];
		ecm[startOffset + 0x1B] ^= mask[0x0D];
		ecm[startOffset + 0x30] ^= mask[0x0C];
		ecm[startOffset + 0x1C] ^= mask[0x0E];
		ecm[startOffset + 0x1E] ^= mask[0x00];
		ecm[startOffset + 0x04] ^= mask[0x00];
		ecm[startOffset + 0x05] ^= mask[0x01];
		ecm[startOffset + 0x1F] ^= mask[0x01];
		ecm[startOffset + 0x2C] ^= mask[0x09];
		ecm[startOffset + 0x20] ^= mask[0x02];
		ecm[startOffset + 0x1D] ^= mask[0x0F];
		ecm[startOffset + 0x23] ^= mask[0x04];
		ecm[startOffset + 0x09] ^= mask[0x05];
		ecm[startOffset + 0x22] ^= mask[0x03];
		ecm[startOffset + 0x24] ^= mask[0x05];
		ecm[startOffset + 0x08] ^= mask[0x04];
		ecm[startOffset + 0x28] ^= mask[0x06];
		ecm[startOffset + 0x29] ^= mask[0x07];
		ecm[startOffset + 0x2A] ^= mask[0x08];
		ecm[startOffset + 0x2E] ^= mask[0x0B];

		for (i = 0; i < ecm[9]; i++)
		{
			ecm[10 + i] = 0x00;
		}
	}
	else if (modeUnmask == 0x04)
	{
		ecm[startOffset + 0x1E] -= ecm[startOffset + 0x0D];
		ecm[startOffset + 0x1D] -= ecm[startOffset + 0x07];
		ecm[startOffset + 0x2B] -= ecm[startOffset + 0x05];
		ecm[startOffset + 0x2D] -= ecm[startOffset + 0x08];
		ecm[startOffset + 0x17] -= ecm[startOffset + 0x04];
		ecm[startOffset + 0x2F] -= ecm[startOffset + 0x0C];
		ecm[startOffset + 0x06] -= ecm[startOffset + 0x0A];
		ecm[startOffset + 0x0B] -= ecm[startOffset + 0x09];

		create_data_unmask_mode_04(ecm + startOffset, data);
		create_hash_mode_04(data, mask);

		// Unmask body
		ecm[startOffset + 0x04] ^= mask[0x00];
		ecm[startOffset + 0x05] ^= mask[0x01];
		ecm[startOffset + 0x07] ^= mask[0x02];
		ecm[startOffset + 0x08] ^= mask[0x03];
		ecm[startOffset + 0x09] ^= mask[0x04];
		ecm[startOffset + 0x0A] ^= mask[0x05];
		ecm[startOffset + 0x0C] ^= mask[0x06];
		ecm[startOffset + 0x0D] ^= mask[0x07];
		ecm[startOffset + 0x0E] ^= mask[0x08];
		ecm[startOffset + 0x10] ^= mask[0x09];
		ecm[startOffset + 0x11] ^= mask[0x0A];
		ecm[startOffset + 0x18] ^= mask[0x0B];
		ecm[startOffset + 0x1A] ^= mask[0x0C];
		ecm[startOffset + 0x1B] ^= mask[0x0D];
		ecm[startOffset + 0x1C] ^= mask[0x0E];
		ecm[startOffset + 0x1F] ^= mask[0x0F];
		ecm[startOffset + 0x22] ^= mask[0x00];
		ecm[startOffset + 0x24] ^= mask[0x01];
		ecm[startOffset + 0x25] ^= mask[0x02];
		ecm[startOffset + 0x26] ^= mask[0x03];
		ecm[startOffset + 0x27] ^= mask[0x04];
		ecm[startOffset + 0x28] ^= mask[0x05];
		ecm[startOffset + 0x29] ^= mask[0x06];
		ecm[startOffset + 0x2A] ^= mask[0x07];
		ecm[startOffset + 0x2C] ^= mask[0x08];
		ecm[startOffset + 0x2E] ^= mask[0x09];
		ecm[startOffset + 0x31] ^= mask[0x0A];

		for (i = 0; i < ecm[9]; i++)
		{
			ecm[10 + i] = 0x00;
		}
	}
	else
	{
		create_hash(data, 24, mask, hashModeEcm);

		// Unmask body
		for (i = 0; i < 16; i++)
		{
			ecm[startOffset + destPos[i]] ^= mask[i & 0x0F];
		}
	}

	// Fix header
	ecm[3] &= 0x0F;
	ecm[3] |= 0x30;
	ecm[8] = 0x00;
	ecm[28 + extraBytesLen] = 0x00;

	// Fix CRC (optional)
	l = (((ecm[1] << 8) + ecm[2]) & 0xFFF) + 3 - 4;

	crc = ccitt32_crc(ecm, l);

	ecm[l + 0] = crc >> 24;
	ecm[l + 1] = crc >> 16;
	ecm[l + 2] = crc >> 8;
	ecm[l + 3] = crc >> 0;

	for (i = 0; i < 11; i++)
	{
		seedEcmCw[i] = ecm[startOffset + seedCwPos[i]];
	}

	return hashModeCw;
}

static void create_cw(uint8_t *seed, uint8_t lenSeed, uint8_t *baseCw, uint8_t val, uint8_t *seedEcmCw,
						uint8_t *cw, int modeDesCsa, int hashMode, int modeCW, uint8_t *ecmBody)
{
	int i;
	uint8_t data[64], hash[16], lenData;
	uint8_t tableFixParity[] =
	{
		0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x07, 0x07,
		0x08, 0x08, 0x0B, 0x0B, 0x0D, 0x0D, 0x0E, 0x0E,
		0x10, 0x10, 0x13, 0x13, 0x15, 0x15, 0x16, 0x16,
		0x19, 0x19, 0x1A, 0x1A, 0x1C, 0x1C, 0x1F, 0x1F,
		0x20, 0x20, 0x23, 0x23, 0x25, 0x25, 0x26, 0x26,
		0x29, 0x29, 0x2A, 0x2A, 0x2C, 0x2C, 0x2F, 0x2F,
		0x31, 0x31, 0x32, 0x32, 0x34, 0x34, 0x37, 0x37,
		0x38, 0x38, 0x3B, 0x3B, 0x3D, 0x3D, 0x3E, 0x3E,
		0x40, 0x40, 0x43, 0x43, 0x45, 0x45, 0x46, 0x46,
		0x49, 0x49, 0x4A, 0x4A, 0x4C, 0x4C, 0x4F, 0x4F,
		0x51, 0x51, 0x52, 0x52, 0x54, 0x54, 0x57, 0x57,
		0x58, 0x58, 0x5B, 0x5B, 0x5D, 0x5D, 0x5E, 0x5E,
		0x61, 0x61, 0x62, 0x62, 0x64, 0x64, 0x67, 0x67,
		0x68, 0x68, 0x6B, 0x6B, 0x6D, 0x6D, 0x6E, 0x6E,
		0x70, 0x70, 0x73, 0x73, 0x75, 0x75, 0x76, 0x76,
		0x79, 0x79, 0x7A, 0x7A, 0x7C, 0x7C, 0x7F, 0x7F,
		0x80, 0x80, 0x83, 0x83, 0x85, 0x85, 0x86, 0x86,
		0x89, 0x89, 0x8A, 0x8A, 0x8C, 0x8C, 0x8F, 0x8F,
		0x91, 0x91, 0x92, 0x92, 0x94, 0x94, 0x97, 0x97,
		0x98, 0x98, 0x9B, 0x9B, 0x9D, 0x9D, 0x9E, 0x9E,
		0xA1, 0xA1, 0xA2, 0xA2, 0xA4, 0xA4, 0xA7, 0xA7,
		0xA8, 0xA8, 0xAB, 0xAB, 0xAD, 0xAD, 0xAE, 0xAE,
		0xB0, 0xB0, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB6,
		0xB9, 0xB9, 0xBA, 0xBA, 0xBC, 0xBC, 0xBF, 0xBF,
		0xC1, 0xC1, 0xC2, 0xC2, 0xC4, 0xC4, 0xC7, 0xC7,
		0xC8, 0xC8, 0xCB, 0xCB, 0xCD, 0xCD, 0xCE, 0xCE,
		0xD0, 0xD0, 0xD3, 0xD3, 0xD5, 0xD5, 0xD6, 0xD6,
		0xD9, 0xD9, 0xDA, 0xDA, 0xDC, 0xDC, 0xDF, 0xDF,
		0xE0, 0xE0, 0xE3, 0xE3, 0xE5, 0xE5, 0xE6, 0xE6,
		0xE9, 0xE9, 0xEA, 0xEA, 0xEC, 0xEC, 0xEF, 0xEF,
		0xF1, 0xF1, 0xF2, 0xF2, 0xF4, 0xF4, 0xF7, 0xF7,
		0xF8, 0xF8, 0xFB, 0xFB, 0xFD, 0xFD, 0xFE, 0xFE
	};

	if (modeCW == 0x03)
	{
		create_data_cw_mode_03(seed, lenSeed, baseCw, val, ecmBody, data);
		create_hash_mode_03(data, hash);

		cw[0] = hash[0x09];
		cw[1] = hash[0x01];
		cw[2] = hash[0x0F];
		cw[3] = hash[0x0E];
		cw[4] = hash[0x04];
		cw[5] = hash[0x02];
		cw[6] = hash[0x05];
		cw[7] = hash[0x0D];
	}
	else if (modeCW == 0x04)
	{
		create_data_cw_mode_04(seed, lenSeed, baseCw, val, ecmBody, data);
		create_hash_mode_04(data, hash);

		cw[0] = hash[0x08];
		cw[1] = hash[0x0F];
		cw[2] = hash[0x02];
		cw[3] = hash[0x0A];
		cw[4] = hash[0x06];
		cw[5] = hash[0x03];
		cw[6] = hash[0x09];
		cw[7] = hash[0x0D];
	}
	else
	{
		lenData = create_data_cw(seed, lenSeed, baseCw, val, seedEcmCw, data);
		create_hash(data, lenData, hash, hashMode);

		for (i = 0; i < 8; i++)
		{
			cw[i] = hash[i];
		}
	}

	if (modeDesCsa == 0) // DES - Fix Parity Bits
	{
		for (i = 0; i < 8; i++)
		{
			cw[i] = tableFixParity[cw[i]];
		}
	}
	else if (modeDesCsa == 1) // CSA - Fix Checksums
	{
		cw[3] = cw[0] + cw[1] + cw[2];
		cw[7] = cw[4] + cw[5] + cw[6];
	}
}

static uint32_t create_channel_hash(uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens)
{
	uint8_t buffer[8];
	uint32_t channel_hash = 0;

	if (ens)
	{
		i2b_buf(2, tsid, buffer);
		i2b_buf(2, onid, buffer + 2);
		i2b_buf(4, ens, buffer + 4);

		channel_hash = crc32(caid, buffer, sizeof(buffer));
	}

	return channel_hash;
}

static uint16_t get_channel_group(uint32_t channel_hash)
{
	uint8_t tmp[2];
	uint16_t group = 0;

	if (channel_hash && emu_find_key('P', channel_hash, 0x00000000, "GROUP", tmp, 2, 0, 0, 0, NULL))
	{
		group = b2i(2, tmp);
	}

	return group;
}

static inline int8_t get_ecm_key(uint8_t *key, uint32_t provider, uint32_t ignore_mask, uint8_t keyIndex, uint32_t keyRef)
{
	return emu_find_key('P', provider, ignore_mask, keyIndex == 1 ? "01" : "00", key, 7, 0, keyRef, 0, NULL);
}

static inline int8_t get_emm_key(uint8_t *key, char *uniqueAddress, uint32_t keyRef, uint32_t *groupId)
{
	return emu_find_key('P', 0, 0xFFFFFFFF, uniqueAddress, key, 7, 0, keyRef, 0, groupId);
}

static const uint8_t PowerVu_A0_S_1[16] =
{
	0x33, 0xA4, 0x44, 0x3C, 0xCA, 0x2E, 0x75, 0x7B,
	0xBC, 0xE6, 0xE5, 0x35, 0xA0, 0x55, 0xC9, 0xA2
};

static const uint8_t PowerVu_A0_S_2[16] =
{
	0x5A, 0xB0, 0x2C, 0xBC, 0xDA, 0x32, 0xE6, 0x92,
	0x40, 0x53, 0x6E, 0xF9, 0x69, 0x11, 0x1E, 0xFB
};

static const uint8_t PowerVu_A0_S_3[16] =
{
	0x4E, 0x18, 0x9B, 0x19, 0x79, 0xFB, 0x01, 0xFA,
	0xE3, 0xE1, 0x28, 0x3D, 0x32, 0xE4, 0x92, 0xEA
};

static const uint8_t PowerVu_A0_S_4[16] =
{
	0x05, 0x6F, 0x37, 0x66, 0x35, 0xE1, 0x58, 0xD0,
	0xB4, 0x6A, 0x97, 0xAE, 0xD8, 0x91, 0x27, 0x56
};

static const uint8_t PowerVu_A0_S_5[16] =
{
	0x7B, 0x26, 0xAD, 0x34, 0x3D, 0x77, 0x39, 0x51,
	0xE0, 0xE0, 0x48, 0x8C, 0x39, 0xF5, 0xE8, 0x47
};

static const uint8_t PowerVu_A0_S_6[16] =
{
	0x74, 0xFA, 0x4D, 0x79, 0x42, 0x39, 0xD1, 0xA4,
	0x99, 0xA3, 0x97, 0x07, 0xDF, 0x14, 0x3A, 0xC4
};

static const uint8_t PowerVu_A0_S_7[16] =
{
	0xC6, 0x1E, 0x3C, 0x24, 0x11, 0x08, 0x5D, 0x6A,
	0xEB, 0x97, 0xB9, 0x25, 0xA7, 0xFA, 0xE9, 0x1A
};

static const uint8_t PowerVu_A0_S_8[16] =
{
	0x9A, 0xAD, 0x72, 0xD7, 0x7C, 0x68, 0x3B, 0x55,
	0x1D, 0x4A, 0xA2, 0xB0, 0x38, 0xB9, 0x56, 0xD0
};

static const uint8_t PowerVu_A0_S_9[32] =
{
	0x61, 0xDA, 0x5F, 0xB7, 0xEB, 0xC6, 0x3F, 0x6C,
	0x09, 0xF3, 0x64, 0x38, 0x33, 0x08, 0xAA, 0x15,
	0xCC, 0xEF, 0x22, 0x64, 0x01, 0x2C, 0x12, 0xDE,
	0xF4, 0x6E, 0x3C, 0xCD, 0x1A, 0x64, 0x63, 0x7C
};

static const uint8_t PowerVu_00_S_1[16] =
{
	0x97, 0x13, 0xEB, 0x6B, 0x04, 0x5E, 0x60, 0x3A,
	0xD9, 0xCC, 0x91, 0xC2, 0x5A, 0xFD, 0xBA, 0x0C
};

static const uint8_t PowerVu_00_S_2[16] =
{
	0x61, 0x3C, 0x03, 0xB0, 0xB5, 0x6F, 0xF8, 0x01,
	0xED, 0xE0, 0xE5, 0xF3, 0x78, 0x0F, 0x0A, 0x73
};

static const uint8_t PowerVu_00_S_3[16] =
{
	0xFD, 0xDF, 0xD2, 0x97, 0x06, 0x14, 0x91, 0xB5,
	0x36, 0xAD, 0xBC, 0xE1, 0xB3, 0x00, 0x66, 0x41
};

static const uint8_t PowerVu_00_S_4[16] =
{
	0x8B, 0xD9, 0x18, 0x0A, 0xED, 0xEE, 0x61, 0x34,
	0x1A, 0x79, 0x80, 0x8C, 0x1E, 0x7F, 0xC5, 0x9F
};

static const uint8_t PowerVu_00_S_5[16] =
{
	0xB0, 0xA1, 0xF2, 0xB8, 0xEA, 0x72, 0xDD, 0xD3,
	0x30, 0x65, 0x2B, 0x1E, 0xE9, 0xE1, 0x45, 0x29
};

static const uint8_t PowerVu_00_S_6[16] =
{
	0x5D, 0xCA, 0x53, 0x75, 0xB2, 0x24, 0xCE, 0xAF,
	0x21, 0x54, 0x9E, 0xBE, 0x02, 0xA9, 0x4C, 0x5D
};

static const uint8_t PowerVu_00_S_7[16] =
{
	0x42, 0x66, 0x72, 0x83, 0x1B, 0x2D, 0x22, 0xC9,
	0xF8, 0x4D, 0xBA, 0xCD, 0xBB, 0x20, 0xBD, 0x6B
};

static const uint8_t PowerVu_00_S_8[16] =
{
	0xC4, 0x0C, 0x6B, 0xD3, 0x6D, 0x94, 0x7E, 0x53,
	0xCE, 0x96, 0xAC, 0x40, 0x2C, 0x7A, 0xD3, 0xA9
};

static const uint8_t PowerVu_00_S_9[32] =
{
	0x31, 0x82, 0x4F, 0x9B, 0xCB, 0x6F, 0x9D, 0xB7,
	0xAE, 0x68, 0x0B, 0xA0, 0x93, 0x15, 0x32, 0xE2,
	0xED, 0xE9, 0x47, 0x29, 0xC2, 0xA8, 0x92, 0xEF,
	0xBA, 0x27, 0x22, 0x57, 0x76, 0x54, 0xC0, 0x59
};

static uint8_t powervu_sbox(uint8_t *input, uint8_t mode)
{
	uint8_t s_index, bit, last_index, last_bit;
	uint8_t const *Sbox1, *Sbox2, *Sbox3, *Sbox4, *Sbox5, *Sbox6, *Sbox7, *Sbox8, *Sbox9;

	if (mode)
	{
		Sbox1 = PowerVu_A0_S_1;
		Sbox2 = PowerVu_A0_S_2;
		Sbox3 = PowerVu_A0_S_3;
		Sbox4 = PowerVu_A0_S_4;
		Sbox5 = PowerVu_A0_S_5;
		Sbox6 = PowerVu_A0_S_6;
		Sbox7 = PowerVu_A0_S_7;
		Sbox8 = PowerVu_A0_S_8;
		Sbox9 = PowerVu_A0_S_9;
	}
	else
	{
		Sbox1 = PowerVu_00_S_1;
		Sbox2 = PowerVu_00_S_2;
		Sbox3 = PowerVu_00_S_3;
		Sbox4 = PowerVu_00_S_4;
		Sbox5 = PowerVu_00_S_5;
		Sbox6 = PowerVu_00_S_6;
		Sbox7 = PowerVu_00_S_7;
		Sbox8 = PowerVu_00_S_8;
		Sbox9 = PowerVu_00_S_9;
	}

	bit = (get_bit(input[2], 0) << 2) | (get_bit(input[3], 4) << 1) | (get_bit(input[5], 3));
	s_index = (get_bit(input[0], 0) << 3) | (get_bit(input[2], 6) << 2) | (get_bit(input[2], 4) << 1) | (get_bit(input[5], 7));
	last_bit = get_bit(Sbox1[s_index], 7 - bit);

	bit = (get_bit(input[5], 0) << 2) | (get_bit(input[4], 0) << 1) | (get_bit(input[6], 2));
	s_index = (get_bit(input[2], 1) << 3) | (get_bit(input[2], 2) << 2) | (get_bit(input[5], 5) << 1) | (get_bit(input[5], 1));
	last_bit = last_bit | (get_bit(Sbox2[s_index], 7 - bit) << 1);

	bit = (get_bit(input[6], 0) << 2) | (get_bit(input[1], 7) << 1) | (get_bit(input[6], 7));
	s_index = (get_bit(input[1], 3) << 3) | (get_bit(input[3], 7) << 2) | (get_bit(input[1], 5) << 1) | (get_bit(input[5], 2));
	last_bit = last_bit | (get_bit(Sbox3[s_index], 7 - bit) << 2);

	bit = (get_bit(input[1], 0) << 2) | (get_bit(input[2], 7) << 1) | (get_bit(input[2], 5));
	s_index = (get_bit(input[6], 3) << 3) | (get_bit(input[6], 4) << 2) | (get_bit(input[6], 6) << 1) | (get_bit(input[3], 5));
	last_index = get_bit(Sbox4[s_index], 7 - bit);

	bit = (get_bit(input[3], 3) << 2) | (get_bit(input[4], 6) << 1) | (get_bit(input[3], 2));
	s_index = (get_bit(input[3], 1) << 3) | (get_bit(input[4], 5) << 2) | (get_bit(input[3], 0) << 1) | (get_bit(input[4], 7));
	last_index = last_index | (get_bit(Sbox5[s_index], 7 - bit) << 1);

	bit = (get_bit(input[5], 4) << 2) | (get_bit(input[4], 4) << 1) | (get_bit(input[1], 2));
	s_index = (get_bit(input[2], 3) << 3) | (get_bit(input[6], 5) << 2) | (get_bit(input[1], 4) << 1) | (get_bit(input[4], 1));
	last_index = last_index | (get_bit(Sbox6[s_index], 7 - bit) << 2);

	bit = (get_bit(input[0], 6) << 2) | (get_bit(input[0], 7) << 1) | (get_bit(input[0], 4));
	s_index = (get_bit(input[0], 5) << 3) | (get_bit(input[0], 3) << 2) | (get_bit(input[0], 1) << 1) | (get_bit(input[0], 2));
	last_index = last_index | (get_bit(Sbox7[s_index], 7 - bit) << 3);

	bit = (get_bit(input[4], 2) << 2) | (get_bit(input[4], 3) << 1) | (get_bit(input[1], 1));
	s_index = (get_bit(input[1], 6) << 3) | (get_bit(input[6], 1) << 2) | (get_bit(input[5], 6) << 1) | (get_bit(input[3], 6));
	last_index = last_index | (get_bit(Sbox8[s_index], 7 - bit) << 4);

	return (get_bit(Sbox9[last_index & 0x1F], 7 - last_bit) & 1) ? 1 : 0;
}

static void powervu_decrypt(uint8_t *data, uint32_t length, uint8_t *key, uint8_t sbox)
{
	uint32_t i;
	int32_t j, k;
	uint8_t curByte, tmpBit;

	for (i = 0; i < length; i++)
	{
		curByte = data[i];

		for (j = 7; j >= 0; j--)
		{
			data[i] = set_bit(data[i], j, (get_bit(curByte, j) ^ powervu_sbox(key, sbox)) ^ get_bit(key[0], 7));
			tmpBit = get_bit(data[i], j) ^ (get_bit(key[6], 0));

			if (tmpBit)
			{
				key[3] ^= 0x10;
			}

			for (k = 6; k > 0; k--)
			{
				key[k] = (key[k] >> 1) | (key[k - 1] << 7);
			}

			key[0] = (key[0] >> 1);
			key[0] = set_bit(key[0], 7, tmpBit);
		}
	}
}

static void expand_des_key(unsigned char *key)
{
	uint8_t i, j, parity;
	uint8_t tmpKey[7];

	memcpy(tmpKey, key, 7);

	key[0] = (tmpKey[0] & 0xFE);
	key[1] = ((tmpKey[0] << 7) | ((tmpKey[1] >> 1) & 0xFE));
	key[2] = ((tmpKey[1] << 6) | ((tmpKey[2] >> 2) & 0xFE));
	key[3] = ((tmpKey[2] << 5) | ((tmpKey[3] >> 3) & 0xFE));
	key[4] = ((tmpKey[3] << 4) | ((tmpKey[4] >> 4) & 0xFE));
	key[5] = ((tmpKey[4] << 3) | ((tmpKey[5] >> 5) & 0xFE));
	key[6] = ((tmpKey[5] << 2) | ((tmpKey[6] >> 6) & 0xFE));
	key[7] = (tmpKey[6] << 1);

	for (i = 0; i < 8; i++)
	{
		parity = 1;

		for (j = 1; j < 8; j++)
		{
			if ((key[i] >> j) & 0x1)
			{
				parity = ~parity & 0x01;
			}
		}

		key[i] |= parity;
	}
}

static uint8_t get_conv_cw_index(uint8_t ecmTag)
{
	switch (ecmTag)
	{
		case PVU_CONVCW_VID_ECM:
			return PVU_CW_VID;

		case PVU_CONVCW_HSD_ECM:
			return PVU_CW_HSD;

		case PVU_CONVCW_A1_ECM:
			return PVU_CW_A1;

		case PVU_CONVCW_A2_ECM:
			return PVU_CW_A2;

		case PVU_CONVCW_A3_ECM:
			return PVU_CW_A3;

		case PVU_CONVCW_A4_ECM:
			return PVU_CW_A4;

		case PVU_CONVCW_UTL_ECM:
			return PVU_CW_UTL;

		case PVU_CONVCW_VBI_ECM:
			return PVU_CW_VBI;

		default:
			return PVU_CW_VBI;
	}
}

static uint16_t get_seed_iv(uint8_t seedType, uint8_t *ecm)
{
	switch (seedType)
	{
		case PVU_CW_VID:
			return ((ecm[0x10] & 0x1F) << 3) | 0;

		case PVU_CW_HSD:
			return ((ecm[0x12] & 0x1F) << 3) | 2;

		case PVU_CW_A1:
			return ((ecm[0x11] & 0x3F) << 3) | 1;

		case PVU_CW_A2:
			return ((ecm[0x13] & 0x3F) << 3) | 1;

		case PVU_CW_A3:
			return ((ecm[0x19] & 0x3F) << 3) | 1;

		case PVU_CW_A4:
			return ((ecm[0x1A] & 0x3F) << 3) | 1;

		case PVU_CW_UTL:
			return ((ecm[0x14] & 0x0F) << 3) | 4;

		case PVU_CW_VBI:
			return (((ecm[0x15] & 0xF8) >> 3) << 3) | 5;

		default:
			return 0;
	}
}

static uint8_t expand_seed(uint8_t seedType, uint8_t *seed)
{
	uint8_t seedLength = 0, i;

	switch (seedType)
	{
		case PVU_CW_VID:
		case PVU_CW_HSD:
			seedLength = 4;
			break;

		case PVU_CW_A1:
		case PVU_CW_A2:
		case PVU_CW_A3:
		case PVU_CW_A4:
			seedLength = 3;
			break;

		case PVU_CW_UTL:
		case PVU_CW_VBI:
			seedLength = 2;
			break;

		default:
			return seedLength;
	}

	for (i = seedLength; i < 7; i++)
	{
		seed[i] = seed[i % seedLength];
	}

	return seedLength;
}

static void calculate_seed(uint8_t seedType, uint8_t *ecm, uint8_t *seedBase,
							uint8_t *key, uint8_t *seed, uint8_t sbox)
{
	uint16_t tmpSeed;

	tmpSeed = get_seed_iv(seedType, ecm + 23);

	seed[0] = (tmpSeed >> 2) & 0xFF;
	seed[1] = ((tmpSeed & 0x3) << 6) | (seedBase[0] >> 2);
	seed[2] = (    seedBase[0] << 6) | (seedBase[1] >> 2);
	seed[3] = (    seedBase[1] << 6) | (seedBase[2] >> 2);
	seed[4] = (    seedBase[2] << 6) | (seedBase[3] >> 2);
	seed[5] = (    seedBase[3] << 6);

	powervu_decrypt(seed, 6, key, sbox);

	seed[0] = (seed[1] << 2) | (seed[2] >> 6);
	seed[1] = (seed[2] << 2) | (seed[3] >> 6);
	seed[2] = (seed[3] << 2) | (seed[4] >> 6);
	seed[3] = (seed[4] << 2) | (seed[5] >> 6);
}

static void calculate_cw(uint8_t seedType, uint8_t *seed, uint8_t csaUsed, uint8_t *convolvedCw,
							uint8_t *cw, uint8_t *baseCw, uint8_t *seedEcmCw, uint8_t hashModeCw,
							uint8_t needsUnmasking, uint8_t xorMode, int modeCW, uint8_t* ecmBody)
{
	int32_t k;
	uint8_t seedLength, val = 0;

	seedLength = expand_seed(seedType, seed);

	if (needsUnmasking && (((modeCW >= 0x00) && (hashModeCw > 0) && (hashModeCw <= 0x27) &&
		(hashModeCw != 0x0B) && (hashModeCw != 0x0C) && (hashModeCw != 0x0D) && (hashModeCw != 0x0E)) ||
		(modeCW == 0x03) || (modeCW == 0x04)))
	{
		switch (seedType)
		{
			case PVU_CW_VID:
				val = 0;
				break;

			case PVU_CW_A1:
			case PVU_CW_A2:
			case PVU_CW_A3:
			case PVU_CW_A4:
				val = 1;
				break;

			case PVU_CW_HSD:
				val = 2;
				break;

			case PVU_CW_UTL:
				val = 4;
				break;

			case PVU_CW_VBI:
				val = 5;
				break;
		}

		create_cw(seed, seedLength, baseCw, val, seedEcmCw, cw, csaUsed, hashModeCw, modeCW, ecmBody);

		if (csaUsed)
		{
			cw[0] = cw[0] ^ convolvedCw[0];
			cw[1] = cw[1] ^ convolvedCw[1];
			cw[2] = cw[2] ^ convolvedCw[2];
			cw[3] = cw[3] ^ convolvedCw[3];
			cw[4] = cw[4] ^ convolvedCw[4];
			cw[5] = cw[5] ^ convolvedCw[5];
			cw[6] = cw[6] ^ convolvedCw[6];
			cw[7] = cw[7] ^ convolvedCw[7];

			cw[3] = cw[0] + cw[1] + cw[2];
			cw[7] = cw[4] + cw[5] + cw[6];
		}
	}
	else
	{
		if (csaUsed)
		{
			for (k = 0; k < 7; k++)
			{
				seed[k] ^= baseCw[k];
			}

			cw[0] = seed[0] ^ convolvedCw[0];
			cw[1] = seed[1] ^ convolvedCw[1];
			cw[2] = seed[2] ^ convolvedCw[2];
			cw[3] = seed[3] ^ convolvedCw[3];
			cw[4] = seed[3] ^ convolvedCw[4];
			cw[5] = seed[4] ^ convolvedCw[5];
			cw[6] = seed[5] ^ convolvedCw[6];
			cw[7] = seed[6] ^ convolvedCw[7];
		}
		else
		{
			if (xorMode == 0)
			{
				for (k = 0; k < 7; k++)
				{
					cw[k] = seed[k] ^ baseCw[k];
				}
			}

			if (xorMode == 1)
			{
				for (k = 0; k < 3; k++)
				{
					cw[k] = seed[k] ^ baseCw[k];
				}

				for (k = 3; k < 7; k++)
				{
					cw[k] = baseCw[k];
				}
			}

			expand_des_key(cw);
		}
	}
}

#ifdef MODULE_STREAMRELAY
int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens, emu_stream_client_key_data *cdata)
#else
int8_t powervu_ecm(uint8_t *ecm, uint8_t *dw, EXTENDED_CW *cw_ex, uint16_t srvid, uint16_t caid, uint16_t tsid, uint16_t onid, uint32_t ens)
#endif
{
	uint32_t i, j, k;
	uint32_t ecmCrc32, keyRef0, keyRef1, keyRef2, channel_hash, group_id = 0;

	uint16_t ecmLen = SCT_LEN(ecm);
	uint16_t nanoLen, channelId, ecmSrvid;

	uint8_t keyIndex, sbox, decrypt_ok, calculateAll, hashModeCw = 0, needsUnmasking, xorMode;
	uint8_t nanoCmd, nanoChecksum, keyType, fixedKey, oddKey, bid, csaUsed, modeCW = 0, offsetBody;

	uint8_t ecmKey[7], tmpEcmKey[7], seedBase[4], baseCw[7], seed[8][8], cw[8][8], convolvedCw[8][8];
	uint8_t ecmPart1[14], ecmPart2[27], unmaskedEcm[ecmLen], seedEcmCw[16];

	//char tmpBuffer1[512];
	char tmpBuffer2[17];

#ifdef MODULE_STREAMRELAY
	emu_stream_cw_item *cw_item;
	int8_t update_global_key = 0;
	int8_t update_global_keys[EMU_STREAM_SERVER_MAX_CONNECTIONS];

	memset(update_global_keys, 0, sizeof(update_global_keys));
#else
#define EMU_STREAM_MAX_AUDIO_SUB_TRACKS 4
#endif

	if (ecmLen < 7)
	{
		return EMU_NOT_SUPPORTED;
	}

	needsUnmasking = (ecm[3] & 0xF0) == 0x50;

	//cs_log_dbg(D_ATR, "ecm1: %s", cs_hexdump(0, ecm, ecmLen, tmpBuffer1, sizeof(tmpBuffer1)));

	if (needsUnmasking)
	{
		hashModeCw = unmask_ecm(ecm, seedEcmCw, &modeCW);
	}

	//cs_log_dbg(D_ATR, "needsUnmasking=%d", needsUnmasking);
	//cs_log_dbg(D_ATR, "ecm2: %s", cs_hexdump(0, ecm, ecmLen, tmpBuffer1, sizeof(tmpBuffer1)));

	memcpy(unmaskedEcm, ecm, ecmLen);

	ecmCrc32 = b2i(4, ecm + ecmLen - 4);

	if (ccitt32_crc(ecm, ecmLen - 4) != ecmCrc32)
	{
		return EMU_CHECKSUM_ERROR;
	}
	ecmLen -= 4;

	for (i = 0; i < 8; i++)
	{
		memset(convolvedCw[i], 0, 8);
	}

	for (i = 3; i + 3 < ecmLen; )
	{
		nanoLen = (((ecm[i] & 0x0F) << 8) | ecm[i + 1]);
		i += 2;

		if (nanoLen > 0)
		{
			nanoLen--;
		}
		nanoCmd = ecm[i++];

		if (i + nanoLen > ecmLen)
		{
			return EMU_NOT_SUPPORTED;
		}

		switch (nanoCmd)
		{
			case 0x27:
				if (nanoLen < 15)
				{
					break;
				}

				nanoChecksum = 0;
				for (j = 4; j < 15; j++)
				{
					nanoChecksum += ecm[i + j];
				}

				if (nanoChecksum != 0)
				{
					break;
				}

				keyType = get_conv_cw_index(ecm[i + 4]);
				memcpy(convolvedCw[keyType], &ecm[i + 6], 8);
				break;

			default:
				break;
		}

		i += nanoLen;
	}

	for (i = 3; i + 3 < ecmLen; )
	{
		nanoLen = (((ecm[i] & 0x0F) << 8) | ecm[i + 1]);
		i += 2;

		if (nanoLen > 0)
		{
			nanoLen--;
		}
		nanoCmd = ecm[i++];

		if (i + nanoLen > ecmLen)
		{
			return EMU_NOT_SUPPORTED;
		}

		switch (nanoCmd)
		{
			case 0x20:
			{
				if (nanoLen < 54)
				{
					break;
				}

				offsetBody = i + 4 + ecm[i + 3];
				i += ecm[i + 3]; // Extra Data Length

				csaUsed = get_bit(ecm[i + 7], 7);
				fixedKey = !get_bit(ecm[i + 6], 5);
				oddKey = get_bit(ecm[i + 6], 4);
				xorMode = get_bit(ecm[i + 6], 0);
				bid = (get_bit(ecm[i + 7], 1) << 1) | get_bit(ecm[i + 7], 0);
				sbox = get_bit(ecm[i + 6], 3);

				keyIndex = (fixedKey << 3) | (bid << 2) | oddKey;
				channelId = b2i(2, ecm + i + 23);
				ecmSrvid = (channelId >> 4) | ((channelId & 0xF) << 12);

				cs_log_dbg(D_ATR, "csaUsed: %d, xorMode: %d, ecmSrvid: %04X (%d), hashModeCw: %d, modeCW: %d",
							csaUsed, xorMode, ecmSrvid, srvid, hashModeCw, modeCW);

				channel_hash = create_channel_hash(caid, tsid, onid, ens);
				group_id = get_channel_group(channel_hash);

				cs_log_dbg(D_ATR, "channel hash: %08X, group id: %04X", channel_hash, group_id);

				decrypt_ok = 0;

				memcpy(ecmPart1, ecm + i + 8, 14);
				memcpy(ecmPart2, ecm + i + 27, 27);

				keyRef0 = 0;
				keyRef1 = 0;
				keyRef2 = 0;

				do
				{
					if (!group_id || !get_ecm_key(ecmKey, group_id << 16, 0x0000FFFF, keyIndex, keyRef0++))
					{
						if (!get_ecm_key(ecmKey, ecmSrvid, 0xFFFF0000, keyIndex, keyRef1++))
						{
							if (!get_ecm_key(ecmKey, channelId, 0xFFFF0000, keyIndex, keyRef2++))
							{
								cs_log("Key not found or invalid: P ****%04X %02X", ecmSrvid, keyIndex);

								if (group_id) // Print only if there is a matching "GROUP" entry
								{
									cs_log("Key not found or invalid: P %04XFFFF %02X", group_id, keyIndex);
								}

								return EMU_KEY_NOT_FOUND;
							}
						}
					}

					powervu_decrypt(ecm + i + 8, 14, ecmKey, sbox);

					if ((ecm[i + 6] != ecm[i + 6 + 7]) || (ecm[i + 6 + 8] != ecm[i + 6 + 15]))
					{
						memcpy(ecm + i + 8, ecmPart1, 14);
						continue;
					}

					memcpy(tmpEcmKey, ecmKey, 7);

					powervu_decrypt(ecm + i + 27, 27, ecmKey, sbox);

					if ((ecm[i + 23] != ecm[i + 23 + 29]) || (ecm[i + 23 + 1] != ecm[i + 23 + 30]))
					{
						memcpy(ecm + i + 8, ecmPart1, 14);
						memcpy(ecm + i + 27, ecmPart2, 27);
						continue;
					}

					decrypt_ok = 1;
				}
				while (!decrypt_ok);

				memcpy(seedBase, ecm + i + 6 + 2, 4);

#ifdef MODULE_STREAMRELAY
				if (cdata == NULL)
				{
					SAFE_MUTEX_LOCK(&emu_fixed_key_srvid_mutex);
					for (j = 0; j < EMU_STREAM_SERVER_MAX_CONNECTIONS; j++)
					{
						if (!stream_server_has_ecm[j] && emu_stream_cur_srvid[j] == srvid)
						{
							update_global_key = 1;
							update_global_keys[j] = 1;
						}
					}
					SAFE_MUTEX_UNLOCK(&emu_fixed_key_srvid_mutex);
				}

				calculateAll = cdata != NULL || update_global_key || cw_ex != NULL;
#else
				calculateAll = cw_ex != NULL;
#endif

				if (calculateAll) // Calculate all seeds
				{
					for (j = 0; j < 8; j++)
					{
						memcpy(ecmKey, tmpEcmKey, 7);
						calculate_seed(j, ecm + i, seedBase, ecmKey, seed[j], sbox);
					}
				}
				else // Calculate only video seed
				{
					memcpy(ecmKey, tmpEcmKey, 7);
					calculate_seed(PVU_CW_VID, ecm + i, seedBase, ecmKey, seed[PVU_CW_VID], sbox);
				}

				memcpy(baseCw, ecm + i + 6 + 8, 7);

				if (calculateAll) // Calculate all CWs
				{
					for (j = 0; j < 8; j++)
					{
						calculate_cw(j, seed[j], csaUsed, convolvedCw[j], cw[j], baseCw, seedEcmCw,
									hashModeCw, needsUnmasking, xorMode, modeCW, unmaskedEcm + offsetBody);

						if (csaUsed)
						{
							for (k = 0; k < 8; k += 4)
							{
								cw[j][k + 3] = ((cw[j][k] + cw[j][k + 1] + cw[j][k + 2]) & 0xFF);
							}
						}

						cs_log_dbg(D_ATR, "calculated cw %d: %s", j,
										cs_hexdump(0, cw[j], 8, tmpBuffer2, sizeof(tmpBuffer2)));
					}

					//cs_log_dbg(D_ATR, "csaUsed=%d, cw: %s cdata=%x, cw_ex=%x",
					//			csaUsed, cs_hexdump(3, cw[0], 8, tmpBuffer1, sizeof(tmpBuffer1)),
					//			(unsigned int)cdata, (unsigned int)cw_ex);

#ifdef MODULE_STREAMRELAY
					if (update_global_key)
					{
						for (j = 0; j < EMU_STREAM_SERVER_MAX_CONNECTIONS; j++)
						{
							if (update_global_keys[j])
							{
								cw_item = (emu_stream_cw_item *)malloc(sizeof(emu_stream_cw_item));
								if (cw_item != NULL)
								{
									cw_item->csa_used = csaUsed;
									cw_item->is_even = ecm[0] == 0x80 ? 1 : 0;
									cs_ftime(&cw_item->write_time);
									add_ms_to_timeb(&cw_item->write_time, cfg.emu_stream_ecm_delay);
									memcpy(cw_item->cw, cw, sizeof(cw));
									ll_append(ll_emu_stream_delayed_keys[j], cw_item);
								}
							}
						}
					}

					if (cdata != NULL)
					{
						for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS + 2; j++)
						{
							if (csaUsed)
							{
								if (ecm[0] == 0x80)
								{
									if (has_dvbcsa_ecm)
									{
										dvbcsa_bs_key_set(cw[j], key_data[cdata->connid].key[j][EVEN]);
									}
								}
								else
								{
									if (has_dvbcsa_ecm)
									{
										dvbcsa_bs_key_set(cw[j], key_data[cdata->connid].key[j][ODD]);
									}
								}

								cdata->csa_used = 1;
							}
							else
							{
								if (ecm[0] == 0x80)
								{
									des_set_key(cw[j], cdata->pvu_des_ks[j][0]);
								}
								else
								{
									des_set_key(cw[j], cdata->pvu_des_ks[j][1]);
								}

								cdata->csa_used = 0;
							}
						}
					}
#endif

					if (cw_ex != NULL)
					{
						cw_ex->mode = CW_MODE_MULTIPLE_CW;

						if (csaUsed)
						{
							cw_ex->algo = CW_ALGO_CSA;
							cw_ex->algo_mode = CW_ALGO_MODE_CBC;
						}
						else
						{
							cw_ex->algo = CW_ALGO_DES;
							cw_ex->algo_mode = CW_ALGO_MODE_ECB;
						}

						for (j = 0; j < EMU_STREAM_MAX_AUDIO_SUB_TRACKS; j++)
						{
							memset(cw_ex->audio[j], 0, 16);

							if (ecm[0] == 0x80)
							{
								memcpy(cw_ex->audio[j], cw[PVU_CW_A1 + j], 8);
							}
							else
							{
								memcpy(&cw_ex->audio[j][8], cw[PVU_CW_A1 + j], 8);
							}
						}

						memset(cw_ex->data, 0, 16);

						if (ecm[0] == 0x80)
						{
							memcpy(cw_ex->data, cw[PVU_CW_HSD], 8);
						}
						else
						{
							memcpy(&cw_ex->data[8], cw[PVU_CW_HSD], 8);
						}
					}
				}
				else // Calculate only video CW
				{
					calculate_cw(PVU_CW_VID, seed[PVU_CW_VID], csaUsed, convolvedCw[PVU_CW_VID],
								cw[PVU_CW_VID], baseCw, seedEcmCw, hashModeCw, needsUnmasking,
								xorMode, modeCW, unmaskedEcm + offsetBody);

					if (csaUsed)
					{
						for (k = 0; k < 8; k += 4)
						{
							cw[PVU_CW_VID][k + 3] = ((cw[PVU_CW_VID][k] + cw[PVU_CW_VID][k + 1] + cw[PVU_CW_VID][k + 2]) & 0xFF);
						}
					}

					cs_log_dbg(D_ATR, "calculated video only cw: %s",
									cs_hexdump(0, cw[PVU_CW_VID], 8, tmpBuffer2, sizeof(tmpBuffer2)));
				}

				memset(dw, 0, 16);

				if (ecm[0] == 0x80)
				{
					memcpy(dw, cw[PVU_CW_VID], 8);
				}
				else
				{
					memcpy(&dw[8], cw[PVU_CW_VID], 8);
				}

				return EMU_OK;
			}

			default:
				break;
		}

		i += nanoLen;
	}

	return EMU_NOT_SUPPORTED;
}

// PowerVu EMM EMU
static void create_data_unmask_emm_mode_03(uint8_t *emmBody, uint8_t *data)
{
	int i;
	uint8_t padding[] =
	{
		0xB3, 0x60, 0x35, 0xC8, 0x5C, 0x26, 0xC1, 0xD0,
		0x88, 0x86, 0x57, 0xB6, 0x45, 0xA7, 0xDF, 0x7E,
		0xF0, 0xA8, 0x49, 0xFB, 0x79, 0x6C, 0xAF, 0xB0
	};

	memcpy(data + 40, padding, 24);

	for (i = 0; i < 5; i++)
	{
		data[0 + i * 8] = emmBody[0x18 + i * 0x1B];
		data[1 + i * 8] = emmBody[0x16 + i * 0x1B];
		data[2 + i * 8] = emmBody[0x07 + i * 0x1B];
		data[3 + i * 8] = emmBody[0x0B + i * 0x1B];
		data[4 + i * 8] = emmBody[0x06 + i * 0x1B];
		data[5 + i * 8] = emmBody[0x19 + i * 0x1B];
		data[6 + i * 8] = emmBody[0x15 + i * 0x1B];
		data[7 + i * 8] = emmBody[0x03 + i * 0x1B];
	}
}

static void create_data_unmask_emm_mode_04(uint8_t *emmBody, uint8_t *data)
{
	int i;
	uint8_t padding[] =
	{
		0x56, 0xC7, 0x05, 0x66, 0xC7, 0x4E, 0xC1, 0xA0,
		0x9E, 0xD1, 0xFE, 0x92, 0xE8, 0xCD, 0x5F, 0xAF,
		0xCF, 0xE5, 0xE9, 0x9E, 0x7A, 0x38, 0xAC, 0x68
	};

	memcpy(data + 0x28, padding, 0x18);

	for (i = 0; i < 5; i++)
	{
		data[0 + i * 8] = emmBody[0x06 + i * 0x1B];
		data[1 + i * 8] = emmBody[0x19 + i * 0x1B];
		data[2 + i * 8] = emmBody[0x16 + i * 0x1B];
		data[3 + i * 8] = emmBody[0x0A + i * 0x1B];
		data[4 + i * 8] = emmBody[0x13 + i * 0x1B];
		data[5 + i * 8] = emmBody[0x05 + i * 0x1B];
		data[6 + i * 8] = emmBody[0x14 + i * 0x1B];
		data[7 + i * 8] = emmBody[0x18 + i * 0x1B];
	}
}

static uint8_t get_mode_unmask_emm(uint8_t *extraData)
{
	uint16_t data = ((uint16_t)extraData[0] << 8) + extraData[1];

	if (data == 0)
	{
		return 0x00;
	}

	switch (data & 0x0881)
	{
		case 0x0080:
		case 0x0881:
			return 0x01;

		case 0x0001:
		case 0x0880:
			return 0x02;

		case 0x0800:
		case 0x0081:
			return 0x03;

		case 0x0000:
		case 0x0801:
			switch (data & 0x9020)
			{
				case 0x8000:
				case 0x9000:
					return 0x04;

				case 0x0020:
				case 0x9020:
					return 0x05;

				case 0x0000:
				case 0x1000:
					return 0x06;

				case 0x1020:
				case 0x8020:
					switch (data & 0x2014)
					{
						case 0x2004:
						case 0x2010:
							return 0x07;

						case 0x0000:
						case 0x0004:
							return 0x08;

						case 0x0014:
						case 0x2014:
							return 0x09;

						case 0x0010:
						case 0x2000:
							return 0x00;
					}
					break;
			}
			break;
	}
	return 0x00;
}

static void unmask_emm(uint8_t *emm)
{
	uint32_t crc, i, l;
	uint8_t hashModeEmm, modeUnmask, data[64], mask[16];

	uint8_t sourcePos[] =
	{
		0x03, 0x0C, 0x0D, 0x11, 0x15, 0x18, 0x1D, 0x1F, 0x25, 0x2A,
		0x32, 0x35, 0x3A, 0x3B, 0x3E, 0x42, 0x47, 0x48, 0x53, 0x58,
		0x5C, 0x61, 0x66, 0x69, 0x71, 0x72, 0x78, 0x7B, 0x81, 0x84
	};

	uint8_t destPos[] =
	{
		0x02, 0x08, 0x0B, 0x0E, 0x13, 0x16, 0x1E, 0x23, 0x28, 0x2B,
		0x2F, 0x33, 0x38, 0x3C, 0x40, 0x44, 0x4A, 0x4D, 0x54, 0x57,
		0x5A, 0x63, 0x68, 0x6A, 0x70, 0x75, 0x76, 0x7D, 0x82, 0x85
	};

	// Create Mask for ECM decryption
	create_data_ecm_emm(emm, sourcePos, 19, 30, data);

	hashModeEmm = emm[8] ^ crc8_calc(data, 30);
	modeUnmask = get_mode_unmask_emm(emm + 16);

	if ((modeUnmask == 0x00) || (modeUnmask > 4))
	{
		create_hash(data, 30, mask, hashModeEmm);

		// Unmask Body
		for (i = 0; i < 30; i++)
		{
			emm[19 + destPos[i]] ^= mask[i & 0x0F];
		}
	}
	else if (modeUnmask == 0x03)
	{
		for (i = 0; i < 5; i++)
		{
			emm[0x13 + 0x03 + i * 0x1B] -= emm[0x13 + 0x0D + i * 0x1B];
			emm[0x13 + 0x06 + i * 0x1B] -= emm[0x13 + 0x1A + i * 0x1B];
			emm[0x13 + 0x07 + i * 0x1B] -= emm[0x13 + 0x10 + i * 0x1B];
			emm[0x13 + 0x0B + i * 0x1B] -= emm[0x13 + 0x17 + i * 0x1B];
			emm[0x13 + 0x15 + i * 0x1B] -= emm[0x13 + 0x05 + i * 0x1B];
			emm[0x13 + 0x16 + i * 0x1B] -= emm[0x13 + 0x0F + i * 0x1B];
			emm[0x13 + 0x18 + i * 0x1B] -= emm[0x13 + 0x14 + i * 0x1B];
			emm[0x13 + 0x19 + i * 0x1B] -= emm[0x13 + 0x04 + i * 0x1B];
		}

		create_data_unmask_emm_mode_03(emm + 0x13, data);
		create_hash_mode_03(data, mask);

		for (i = 0; i < 5; i++)
		{
			emm[0x13 + 0x14 + i * 0x1B] ^= mask[0x00];
			emm[0x13 + 0x0F + i * 0x1B] ^= mask[0x01];
			emm[0x13 + 0x10 + i * 0x1B] ^= mask[0x02];
			emm[0x13 + 0x17 + i * 0x1B] ^= mask[0x03];
			emm[0x13 + 0x1A + i * 0x1B] ^= mask[0x04];
			emm[0x13 + 0x04 + i * 0x1B] ^= mask[0x05];
			emm[0x13 + 0x05 + i * 0x1B] ^= mask[0x06];
			emm[0x13 + 0x0D + i * 0x1B] ^= mask[0x07];
			emm[0x13 + 0x09 + i * 0x1B] ^= mask[0x08];
			emm[0x13 + 0x0A + i * 0x1B] ^= mask[0x09];
			emm[0x13 + 0x0E + i * 0x1B] ^= mask[0x0A];
			emm[0x13 + 0x11 + i * 0x1B] ^= mask[0x0B];
			emm[0x13 + 0x12 + i * 0x1B] ^= mask[0x0C];
			emm[0x13 + 0x13 + i * 0x1B] ^= mask[0x0D];
			emm[0x13 + 0x08 + i * 0x1B] ^= mask[0x0E];
			emm[0x13 + 0x0C + i * 0x1B] ^= mask[0x0F];
		}
	}
	else if (modeUnmask == 0x04)
	{
		for (i = 0; i < 5; i++)
		{
			emm[0x13 + 0x05 + i * 0x1B] -= emm[0x13 + 0x04 + i * 0x1B];
			emm[0x13 + 0x06 + i * 0x1B] -= emm[0x13 + 0x0B + i * 0x1B];
			emm[0x13 + 0x0A + i * 0x1B] -= emm[0x13 + 0x17 + i * 0x1B];
			emm[0x13 + 0x13 + i * 0x1B] -= emm[0x13 + 0x1A + i * 0x1B];
			emm[0x13 + 0x14 + i * 0x1B] -= emm[0x13 + 0x0E + i * 0x1B];
			emm[0x13 + 0x16 + i * 0x1B] -= emm[0x13 + 0x15 + i * 0x1B];
			emm[0x13 + 0x18 + i * 0x1B] -= emm[0x13 + 0x08 + i * 0x1B];
			emm[0x13 + 0x19 + i * 0x1B] -= emm[0x13 + 0x12 + i * 0x1B];
		}

		create_data_unmask_emm_mode_04(emm + 0x13, data);
		create_hash_mode_04(data, mask);

		for (i = 0; i < 5; i++)
		{
			emm[0x13 + 0x0B + i * 0x1B] ^= mask[0x00];
			emm[0x13 + 0x12 + i * 0x1B] ^= mask[0x01];
			emm[0x13 + 0x15 + i * 0x1B] ^= mask[0x02];
			emm[0x13 + 0x17 + i * 0x1B] ^= mask[0x03];
			emm[0x13 + 0x1A + i * 0x1B] ^= mask[0x04];
			emm[0x13 + 0x04 + i * 0x1B] ^= mask[0x05];
			emm[0x13 + 0x0E + i * 0x1B] ^= mask[0x06];
			emm[0x13 + 0x08 + i * 0x1B] ^= mask[0x07];
			emm[0x13 + 0x09 + i * 0x1B] ^= mask[0x08];
			emm[0x13 + 0x0C + i * 0x1B] ^= mask[0x09];
			emm[0x13 + 0x03 + i * 0x1B] ^= mask[0x0A];
			emm[0x13 + 0x0F + i * 0x1B] ^= mask[0x0B];
			emm[0x13 + 0x10 + i * 0x1B] ^= mask[0x0C];
			emm[0x13 + 0x07 + i * 0x1B] ^= mask[0x0D];
			emm[0x13 + 0x0D + i * 0x1B] ^= mask[0x0E];
			emm[0x13 + 0x11 + i * 0x1B] ^= mask[0x0F];
		}
	}
	else
	{
		cs_log("A new unknown emm mode [%d] is in use.", modeUnmask);
	}

	// Fix Header
	emm[3] &= 0x0F;
	emm[3] |= 0x10;
	emm[8] = 0x00;

	// Fix CRC (optional)
	l = (((emm[1] << 8) + emm[2]) & 0xFFF) + 3 - 4;
	crc = ccitt32_crc(emm, l);

	emm[l + 0] = crc >> 24;
	emm[l + 1] = crc >> 16;
	emm[l + 2] = crc >> 8;
	emm[l + 3] = crc >> 0;
}

static int8_t update_ecm_keys_by_group(uint32_t groupId, uint8_t keyIndex, uint8_t *Key, uint32_t uniqueAddress)
{
	int8_t ret = 0;
	uint8_t oldKey[7];
	uint32_t foundProvider = 0, keyRef = 0;
	char indexStr[3], uaInfo[13];

	snprintf(indexStr, 3, "%02X", keyIndex);
	snprintf(uaInfo, 13, "UA: %08X", uniqueAddress);

	SAFE_MUTEX_LOCK(&emu_key_data_mutex);
	while (emu_find_key('P', groupId << 16 & 0xFFFF0000, 0x0000FFFF, indexStr, oldKey, 7, 0, keyRef, 0, &foundProvider))
	{
		keyRef++;

		if (memcmp(oldKey, Key, 7) == 0) // New ECM key already in the db
		{
			continue;
		}

		if (emu_set_key('P', foundProvider, indexStr, Key, 7, 1, uaInfo, NULL))
		{
			ret = 1;
		}
	}
	SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);

	return ret;
}

int8_t powervu_emm(uint8_t *emm, uint32_t *keysAdded)
{
	uint8_t emmInfo, emmType, decryptOk = 0;
	uint8_t emmKey[7], tmpEmmKey[7], tmp[26];
	uint16_t emmLen = SCT_LEN(emm);
	uint32_t i, uniqueAddress, groupId, keyRef = 0;
	//uint32_t emmCrc32;
	char keyName[EMU_MAX_CHAR_KEYNAME], keyValue[16];

	if (emmLen < 50)
	{
		return EMU_NOT_SUPPORTED;
	}

	// Check if unmasking is needed
	if ((emm[3] & 0xF0) == 0x50)
	{
		unmask_emm(emm);
	}

	// looks like checksum does not work for all EMMs
	//emmCrc32 = b2i(4, emm+emmLen-4);
	//
	//if(ccitt32_crc(emm, emmLen-4) != emmCrc32)
	//{
	//	return EMU_CHECKSUM_ERROR;
	//}
	emmLen -= 4;

	uniqueAddress = b2i(4, emm + 12);
	snprintf(keyName, EMU_MAX_CHAR_KEYNAME, "%.8X", uniqueAddress);

	do
	{
		if (!get_emm_key(emmKey, keyName, keyRef++, &groupId))
		{
			//cs_log_dbg(D_ATR, "EMM key for UA %s is missing", keyName);
			return EMU_KEY_NOT_FOUND;
		}

		for (i = 19; i + 27 <= emmLen; i += 27)
		{
			emmInfo = emm[i];

			if (!get_bit(emmInfo, 7))
			{
				continue;
			}

			//keyNb = emm[i] & 0x0F;

			memcpy(tmp, emm + i + 1, 26);
			memcpy(tmpEmmKey, emmKey, 7);
			powervu_decrypt(emm + i + 1, 26, tmpEmmKey, 0);

			if ((emm[13] != emm[i + 24]) || (emm[14] != emm[i + 25]) || (emm[15] != emm[i + 26]))
			{
				memcpy(emm + i + 1, tmp, 26);
				memcpy(tmpEmmKey, emmKey, 7);
				powervu_decrypt(emm + i + 1, 26, tmpEmmKey, 1);

				if ((emm[13] != emm[i + 24]) || (emm[14] != emm[i + 25]) || (emm[15] != emm[i + 26]))
				{
					memcpy(emm + i + 1, tmp, 26);
					memcpy(tmpEmmKey, emmKey, 7);
					continue;
				}
			}

			decryptOk = 1;

			emmType = emm[i + 2] & 0x7F;

			if (emmType > 1)
			{
				continue;
			}

			if (emm[i + 3] == 0 && emm[i + 4] == 0)
			{
				cs_hexdump(0, &emm[i + 3], 7, keyValue, sizeof(keyValue));
				cs_log("Key found in EMM: P %04X**** %02X %s -> REJECTED (looks invalid) UA: %08X",
						groupId, emmType, keyValue, uniqueAddress);
				continue;
			}

			update_ecm_keys_by_group(groupId, emmType, &emm[i + 3], uniqueAddress);

			(*keysAdded)++;
			cs_hexdump(0, &emm[i + 3], 7, keyValue, sizeof(keyValue));
			cs_log("Key found in EMM: P %04X**** %02X %s ; UA: %08X", groupId, emmType, keyValue, uniqueAddress);
		}

	} while (!decryptOk);

	return EMU_OK;
}

int8_t powervu_get_hexserials(uint8_t hexserials[][4], uint32_t maxCount, uint16_t srvid)
{
	//srvid == 0xFFFF -> get all

	int8_t alreadyAdded;
	uint8_t tmp[4];
	uint32_t i, j, k, groupid, length, count = 0;
	KeyDataContainer *KeyDB;

	KeyDB = emu_get_key_container('P');
	if (KeyDB == NULL)
	{
		return 0;
	}

	for (i = 0; i < KeyDB->keyCount && count < maxCount; i++)
	{
		if (KeyDB->EmuKeys[i].provider <= 0x0000FFFF) // skip EMM keys
		{
			continue;
		}

		if (srvid != 0xFFFF && (KeyDB->EmuKeys[i].provider & 0x0000FFFF) != srvid)
		{
			continue;
		}

		// This "groupid" has an ECM key with our "srvid"
		// (in ECM keys "groupid" is top 16 bits)
		groupid = KeyDB->EmuKeys[i].provider >> 16;

		for (j = 0; j < KeyDB->keyCount && count < maxCount; j++)
		{
			// Skip EMM keys belonging to other groups
			// (in EMM keys "groupid" is bottom 16 bits)
			if (KeyDB->EmuKeys[j].provider != groupid)
			{
				continue;
			}

			length = cs_strlen(KeyDB->EmuKeys[j].keyName);

			if (length < 3)
			{
				continue;
			}

			if (length > 8)
			{
				length = 8;
			}

			memset(tmp, 0, 4);
			char_to_bin(tmp + (4 - (length / 2)), KeyDB->EmuKeys[j].keyName, length);

			for (k = 0, alreadyAdded = 0; k < count; k++)
			{
				if (!memcmp(hexserials[k], tmp, 4))
				{
					alreadyAdded = 1;
					break;
				}
			}

			if (!alreadyAdded)
			{
				memcpy(hexserials[count], tmp, 4);
				count++;
			}
		}
	}

	return count;
}

int8_t powervu_get_hexserials_new(uint8_t hexserials[][4], uint32_t maxCount, uint16_t caid,
									uint16_t tsid, uint16_t onid, uint32_t ens)
{
	int8_t alreadyAdded;
	uint8_t tmp[4];
	uint32_t i, j, channel_hash, group_id, length, count = 0;
	KeyDataContainer *KeyDB;

	KeyDB = emu_get_key_container('P');
	if (KeyDB == NULL)
	{
		return 0;
	}

	channel_hash = create_channel_hash(caid, tsid, onid, ens);
	group_id = get_channel_group(channel_hash);

	if (group_id == 0) // No group found for this hash
	{
		return 0;
	}

	for (i = 0; i < KeyDB->keyCount && count < maxCount; i++)
	{
		// Skip EMM keys belonging to other groups
		// (in EMM keys "groupid" is bottom 16 bits)
		if (KeyDB->EmuKeys[i].provider != group_id)
		{
			continue;
		}

		length = cs_strlen(KeyDB->EmuKeys[i].keyName);

		if (length < 3)
		{
			continue;
		}

		if (length > 8)
		{
			length = 8;
		}

		memset(tmp, 0, 4);
		char_to_bin(tmp + (4 - (length / 2)), KeyDB->EmuKeys[i].keyName, length);

		for (j = 0, alreadyAdded = 0; j < count; j++)
		{
			if (!memcmp(hexserials[j], tmp, 4))
			{
				alreadyAdded = 1;
				break;
			}
		}

		if (!alreadyAdded)
		{
			memcpy(hexserials[count], tmp, 4);
			count++;
		}
	}

	return count;
}

#endif // WITH_EMU
