#if defined _smlib_crypt_included
	#endinput
#endif
#define _smlib_crypt_included

#include <sourcemod>

/**********************************************************************************
 *
 * Base64 Encoding/Decoding Functions
 * All Credits to to SirLamer & ScriptCoderPro
 * Taken from http://forums.alliedmods.net/showthread.php?t=101764
 * 
 ***********************************************************************************/

// The Base64 encoding table
static const String:base64_sTable[] =
  // 0000000000111111111122222222223333333333444444444455555555556666
  // 0123456789012345678901234567890123456789012345678901234567890123
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// The Base64 decoding table
static const base64_decodeTable[] = {
//  0   1   2   3   4   5   6   7   8   9
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   //   0 -   9
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   //  10 -  19
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   //  20 -  29
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   //  30 -  39
    0,  0,  0,  62, 0,  0,  0,  63, 52, 53,  //  40 -  49
    54, 55, 56, 57, 58, 59, 60, 61, 0,  0,   //  50 -  59
    0,  0,  0,  0,  0,  0,  1,  2,  3,  4,   //  60 -  69
    5,  6,  7,  8,  9,  10, 11, 12, 13, 14,  //  70 -  79
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  //  80 -  89
    25, 0,  0,  0,  0,  0,  0,  26, 27, 28,  //  90 -  99
    29, 30, 31, 32, 33, 34, 35, 36, 37, 38,  // 100 - 109
    39, 40, 41, 42, 43, 44, 45, 46, 47, 48,  // 110 - 119
    49, 50, 51, 0,  0,  0,  0,  0,  0,  0,   // 120 - 129
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 130 - 139
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 140 - 149
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 150 - 159
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 160 - 169
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 170 - 179
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 180 - 189
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 190 - 199
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 200 - 209
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 210 - 219
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 220 - 229
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 230 - 239
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,   // 240 - 249
    0,  0,  0,  0,  0,  0                    // 250 - 256
};

/*
 * For some reason the standard demands a string in 24-bit (3 character) intervals.
 * This fill character is used to identify unused bytes at the end of the string.
 */
static const base64_cFillChar			= '=';

// The conversion characters between the standard and URL-compliance Base64 protocols
static const String:base64_mime_chars[]	= "+/=";
static const String:base64_url_chars[]	= "-_.";

/*
 * Encodes a string or binary data into Base64
 *
 * @param sString		The input string or binary data to be encoded.
 * @param sResult		The storage buffer for the Base64-encoded result.
 * @param len			The maximum length of the storage buffer, in characters/bytes.
 * @param sourcelen 	(optional): The number of characters or length in bytes to be read from the input source.
 *						This is not needed for a text string, but is important for binary data since there is no end-of-line character.
 * @return				The length of the written Base64 string, in bytes.
 */			
stock Crypt_Base64Encode(const String:sString[], String:sResult[], len, sourcelen=0)
{
	new nLength;	// The string length to be read from the input
	new resPos;		// The string position in the result buffer

	// If the read length was specified, use it; otherwise, pull the string length from the input.
	if (sourcelen > 0) {
		nLength = sourcelen;
	}
	else {
		nLength = strlen(sString);
	}

	// Loop through and generate the Base64 encoded string
	// NOTE: This performs the standard encoding process.  Do not manipulate the logic within this loop.
	for (new nPos = 0; nPos < nLength; nPos++) {
		new cCode;

		cCode = (sString[nPos] >> 2) & 0x3f;

		resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_sTable[cCode]);

		cCode = (sString[nPos] << 4) & 0x3f;
		if (++nPos < nLength) {
			cCode |= (sString[nPos] >> 4) & 0x0f;
		}
		resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_sTable[cCode]);

		if ( nPos < nLength ) {
			cCode = (sString[nPos] << 2) & 0x3f;
			if (++nPos < nLength) {
				cCode |= (sString[nPos] >> 6) & 0x03;
			}

			resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_sTable[cCode]);
		}
		else {
			nPos++;
			resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_cFillChar);
		}

		if (nPos < nLength) {
			cCode = sString[nPos] & 0x3f;
			resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_sTable[cCode]);
		}
		else {
			resPos += FormatEx(sResult[resPos], len - resPos, "%c", base64_cFillChar);
		}
	}

	return resPos;
}


/*
 * Decodes a Base64 string.
 *
 * @param sString		The input string in compliant Base64 format to be decoded.
 * @param sResult		The storage buffer for the decoded text strihg or binary data.
 * @param len			The maximum length of the storage buffer, in characters/bytes.
 * @return				The length of the decoded data, in bytes.
 */
stock Crypt_Base64Decode(const String:sString[], String:sResult[], len)
{
	new nLength = strlen(sString);	// The string length to be read from the input
	new resPos;						// The string position in the result buffer

	// Loop through and generate the Base64 encoded string
	// NOTE: This performs the standard encoding process.  Do not manipulate the logic within this loop.
	for (new nPos = 0; nPos < nLength; nPos++) {

		new c, c1;

		c	= base64_decodeTable[sString[nPos++]];
		c1	= base64_decodeTable[sString[nPos]];

		c = (c << 2) | ((c1 >> 4) & 0x3);

		resPos += FormatEx(sResult[resPos], len - resPos, "%c", c);

		if (++nPos < nLength) {

			c = sString[nPos];

			if (c == base64_cFillChar)
				break;

			c = base64_decodeTable[sString[nPos]];
			c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);

			resPos += FormatEx(sResult[resPos], len - resPos, "%c", c1);
		}

		if (++nPos < nLength) {

			c1 = sString[nPos];

			if (c1 == base64_cFillChar)
				break;

			c1 = base64_decodeTable[sString[nPos]];
			c = ((c << 6) & 0xc0) | c1;

			resPos += FormatEx(sResult[resPos], len - resPos, "%c", c);
		}
	}

	return resPos;
}


/*
 * Converts a standards-compliant Base64 string to the commonly accepted URL-compliant alternative.
 * Note: The result will be the same length as the input string as long as the output buffer is large enough.
 *
 * @param sString		The standards-compliant Base64 input string to converted.
 * @param sResult		The storage buffer for the URL-compliant result.
 * @param len			The maximum length of the storage buffer in characters/bytes.
 * @return				Number of cells written.
 */
stock Crypt_Base64MimeToUrl(const String:sString[], String:sResult[], len)
{
	new chars_len = sizeof(base64_mime_chars);	// Length of the two standards vs. URL character lists
	new nLength;								// The string length to be read from the input
	new temp_char;								// Buffer character

	nLength = strlen(sString);

	new String:sTemp[nLength+1]; // Buffer string

	// Loop through string
	for (new i = 0; i < nLength; i++) {
		temp_char = sString[i];

		for (new j = 0; j < chars_len; j++) {

			if(temp_char == base64_mime_chars[j]) {
				temp_char = base64_url_chars[j];
				break;
			}
		}

		sTemp[i] = temp_char;
	}

	sTemp[nLength] = '\0';

	return strcopy(sResult, len, sTemp);
}

/*
 * Base64UrlToMime(String:sResult[], len, const String:sString[], sourcelen)
 * Converts a URL-compliant Base64 string to the standards-compliant version.
 * Note: The result will be the same length as the input string as long as the output buffer is large enough.
 *
 * @param sString		The URL-compliant Base64 input string to converted.
 * @param sResult		The storage buffer for the standards-compliant result.
 * @param len			The maximum length of the storage buffer in characters/bytes.
 * @return				Number of cells written.
 */
stock Crypt_Base64UrlToMime(const String:sString[], String:sResult[], len)
{
	new chars_len = sizeof(base64_mime_chars);	// Length of the two standards vs. URL character lists
	new nLength;								// The string length to be read from the input
	new temp_char;								// Buffer character

	nLength = strlen(sString);

	new String:sTemp[nLength+1]; // Buffer string
	
	// Loop through string
	for (new i = 0; i < nLength; i++) {
		temp_char = sString[i];
		for (new j = 0; j < chars_len; j++) {
			if (temp_char == base64_url_chars[j]) {
				temp_char = base64_mime_chars[j];
				break;
			}
		}

		sTemp[i] = temp_char;
	}

	sTemp[nLength] = '\0';

	return strcopy(sResult, len, sTemp);
}

/**********************************************************************************
 *
 * MD5 Encoding Functions
 * All Credits go to sslice
 * RSA Data Security, Inc. MD5 Message Digest Algorithm
 * Taken from http://forums.alliedmods.net/showthread.php?t=67683
 * 
 ***********************************************************************************/

/*
 * Calculate the md5 hash of a string.
 * 
 * @param str			Input String
 * @param output		Output String Buffer
 * @param maxlen		Size of the Output String Buffer
 * @noreturn
 */
stock Crypt_MD5(const String:str[], String:output[], maxlen)
{
	decl x[2];
	decl buf[4];
	decl input[64];
	new i, ii;

	new len = strlen(str);

	// MD5Init
	x[0] = x[1] = 0;
	buf[0] = 0x67452301;
	buf[1] = 0xefcdab89;
	buf[2] = 0x98badcfe;
	buf[3] = 0x10325476;

	// MD5Update
	new in[16];

	in[14] = x[0];
	in[15] = x[1];

	new mdi = (x[0] >>> 3) & 0x3F;

	if ((x[0] + (len << 3)) < x[0]) {
		x[1] += 1;
	}

	x[0] += len << 3;
	x[1] += len >>> 29;

	new c = 0;
	while (len--) {
		input[mdi] = str[c];
		mdi += 1;
		c += 1;
		
		if (mdi == 0x40) {

			for (i = 0, ii = 0; i < 16; ++i, ii += 4)
			{
				in[i] = (input[ii + 3] << 24) | (input[ii + 2] << 16) | (input[ii + 1] << 8) | input[ii];
			}

			// Transform
			MD5Transform(buf, in);
			
			mdi = 0;
		}
	}

	// MD5Final
	new padding[64] = {
		0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	new inx[16];
	inx[14] = x[0];
	inx[15] = x[1];

	mdi = (x[0] >>> 3) & 0x3F;

	len = (mdi < 56) ? (56 - mdi) : (120 - mdi);
	in[14] = x[0];
	in[15] = x[1];

	mdi = (x[0] >>> 3) & 0x3F;

	if ((x[0] + (len << 3)) < x[0]) {
		x[1] += 1;
	}

	x[0] += len << 3;
	x[1] += len >>> 29;

	c = 0;
	while (len--) {
		input[mdi] = padding[c];
		mdi += 1;
		c += 1;
		
		if (mdi == 0x40) {

			for (i = 0, ii = 0; i < 16; ++i, ii += 4) {
				in[i] = (input[ii + 3] << 24) | (input[ii + 2] << 16) | (input[ii + 1] << 8) | input[ii];
			}

			// Transform
			MD5Transform(buf, in);
			
			mdi = 0;
		}
	}

	for (i = 0, ii = 0; i < 14; ++i, ii += 4) {
		inx[i] = (input[ii + 3] << 24) | (input[ii + 2] << 16) | (input[ii + 1] << 8) | input[ii];
	}

	MD5Transform(buf, inx);

	new digest[16];
	for (i = 0, ii = 0; i < 4; ++i, ii += 4) {
		digest[ii] = (buf[i]) & 0xFF;
		digest[ii + 1] = (buf[i] >>> 8) & 0xFF;
		digest[ii + 2] = (buf[i] >>> 16) & 0xFF;
		digest[ii + 3] = (buf[i] >>> 24) & 0xFF;
	}

	FormatEx(output, maxlen, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
		digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
		digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
}

static stock MD5Transform_FF(&a, &b, &c, &d, x, s, ac)
	{
	a += (((b) & (c)) | ((~b) & (d))) + x + ac;
	a = (((a) << (s)) | ((a) >>> (32-(s))));
	a += b;
}

static stock MD5Transform_GG(&a, &b, &c, &d, x, s, ac)
	{
	a += (((b) & (d)) | ((c) & (~d))) + x + ac;
	a = (((a) << (s)) | ((a) >>> (32-(s))));
	a += b;
}

static stock MD5Transform_HH(&a, &b, &c, &d, x, s, ac)
	{
	a += ((b) ^ (c) ^ (d)) + x + ac;
	a = (((a) << (s)) | ((a) >>> (32-(s))));
	a += b;
}

static stock MD5Transform_II(&a, &b, &c, &d, x, s, ac)
{
	a += ((c) ^ ((b) | (~d))) + x + ac;
	a = (((a) << (s)) | ((a) >>> (32-(s))));
	a += b;
}

static stock MD5Transform(buf[], in[])
{
	new a = buf[0];
	new b = buf[1];
	new c = buf[2];
	new d = buf[3];

	MD5Transform_FF(a, b, c, d, in[0], 7, 0xd76aa478);
	MD5Transform_FF(d, a, b, c, in[1], 12, 0xe8c7b756);
	MD5Transform_FF(c, d, a, b, in[2], 17, 0x242070db);
	MD5Transform_FF(b, c, d, a, in[3], 22, 0xc1bdceee);
	MD5Transform_FF(a, b, c, d, in[4], 7, 0xf57c0faf);
	MD5Transform_FF(d, a, b, c, in[5], 12, 0x4787c62a);
	MD5Transform_FF(c, d, a, b, in[6], 17, 0xa8304613);
	MD5Transform_FF(b, c, d, a, in[7], 22, 0xfd469501);
	MD5Transform_FF(a, b, c, d, in[8], 7, 0x698098d8);
	MD5Transform_FF(d, a, b, c, in[9], 12, 0x8b44f7af);
	MD5Transform_FF(c, d, a, b, in[10], 17, 0xffff5bb1);
	MD5Transform_FF(b, c, d, a, in[11], 22, 0x895cd7be);
	MD5Transform_FF(a, b, c, d, in[12], 7, 0x6b901122);
	MD5Transform_FF(d, a, b, c, in[13], 12, 0xfd987193);
	MD5Transform_FF(c, d, a, b, in[14], 17, 0xa679438e);
	MD5Transform_FF(b, c, d, a, in[15], 22, 0x49b40821);

	MD5Transform_GG(a, b, c, d, in[1], 5, 0xf61e2562);
	MD5Transform_GG(d, a, b, c, in[6], 9, 0xc040b340);
	MD5Transform_GG(c, d, a, b, in[11], 14, 0x265e5a51);
	MD5Transform_GG(b, c, d, a, in[0], 20, 0xe9b6c7aa);
	MD5Transform_GG(a, b, c, d, in[5], 5, 0xd62f105d);
	MD5Transform_GG(d, a, b, c, in[10], 9, 0x02441453);
	MD5Transform_GG(c, d, a, b, in[15], 14, 0xd8a1e681);
	MD5Transform_GG(b, c, d, a, in[4], 20, 0xe7d3fbc8);
	MD5Transform_GG(a, b, c, d, in[9], 5, 0x21e1cde6);
	MD5Transform_GG(d, a, b, c, in[14], 9, 0xc33707d6);
	MD5Transform_GG(c, d, a, b, in[3], 14, 0xf4d50d87);
	MD5Transform_GG(b, c, d, a, in[8], 20, 0x455a14ed);
	MD5Transform_GG(a, b, c, d, in[13], 5, 0xa9e3e905);
	MD5Transform_GG(d, a, b, c, in[2], 9, 0xfcefa3f8);
	MD5Transform_GG(c, d, a, b, in[7], 14, 0x676f02d9);
	MD5Transform_GG(b, c, d, a, in[12], 20, 0x8d2a4c8a);

	MD5Transform_HH(a, b, c, d, in[5], 4, 0xfffa3942);
	MD5Transform_HH(d, a, b, c, in[8], 11, 0x8771f681);
	MD5Transform_HH(c, d, a, b, in[11], 16, 0x6d9d6122);
	MD5Transform_HH(b, c, d, a, in[14], 23, 0xfde5380c);
	MD5Transform_HH(a, b, c, d, in[1], 4, 0xa4beea44);
	MD5Transform_HH(d, a, b, c, in[4], 11, 0x4bdecfa9);
	MD5Transform_HH(c, d, a, b, in[7], 16, 0xf6bb4b60);
	MD5Transform_HH(b, c, d, a, in[10], 23, 0xbebfbc70);
	MD5Transform_HH(a, b, c, d, in[13], 4, 0x289b7ec6);
	MD5Transform_HH(d, a, b, c, in[0], 11, 0xeaa127fa);
	MD5Transform_HH(c, d, a, b, in[3], 16, 0xd4ef3085);
	MD5Transform_HH(b, c, d, a, in[6], 23, 0x04881d05);
	MD5Transform_HH(a, b, c, d, in[9], 4, 0xd9d4d039);
	MD5Transform_HH(d, a, b, c, in[12], 11, 0xe6db99e5);
	MD5Transform_HH(c, d, a, b, in[15], 16, 0x1fa27cf8);
	MD5Transform_HH(b, c, d, a, in[2], 23, 0xc4ac5665);

	MD5Transform_II(a, b, c, d, in[0], 6, 0xf4292244);
	MD5Transform_II(d, a, b, c, in[7], 10, 0x432aff97);
	MD5Transform_II(c, d, a, b, in[14], 15, 0xab9423a7);
	MD5Transform_II(b, c, d, a, in[5], 21, 0xfc93a039);
	MD5Transform_II(a, b, c, d, in[12], 6, 0x655b59c3);
	MD5Transform_II(d, a, b, c, in[3], 10, 0x8f0ccc92);
	MD5Transform_II(c, d, a, b, in[10], 15, 0xffeff47d);
	MD5Transform_II(b, c, d, a, in[1], 21, 0x85845dd1);
	MD5Transform_II(a, b, c, d, in[8], 6, 0x6fa87e4f);
	MD5Transform_II(d, a, b, c, in[15], 10, 0xfe2ce6e0);
	MD5Transform_II(c, d, a, b, in[6], 15, 0xa3014314);
	MD5Transform_II(b, c, d, a, in[13], 21, 0x4e0811a1);
	MD5Transform_II(a, b, c, d, in[4], 6, 0xf7537e82);
	MD5Transform_II(d, a, b, c, in[11], 10, 0xbd3af235);
	MD5Transform_II(c, d, a, b, in[2], 15, 0x2ad7d2bb);
	MD5Transform_II(b, c, d, a, in[9], 21, 0xeb86d391);

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

/**********************************************************************************
 *
 * RC4 Encoding Functions
 * All Credits go to SirLamer and Raydan
 * Taken from http://forums.alliedmods.net/showthread.php?t=101834
 * 
 ***********************************************************************************/

/*
 * Encrypts a text string using RC4.
 * Note: This function is NOT binary safe.
 * Use EncodeRC4Binary to encode binary data.
 *
 * @param input			The source data to be encrypted.
 * @param pwd			The password/key used to encode and decode the data.
 * @param output		The encoded result.
 * @param maxlen		The maximum length of the output buffer.
 *
 * @noreturn
 */
stock Crypt_RC4Encode(const String:input[], const String:pwd[], String:output[], maxlen)
{
	decl pwd_len,str_len,i,j,a,k;
	decl key[256];
	decl box[256];
	decl tmp;

	pwd_len = strlen(pwd);
	str_len = strlen(input);

	if (pwd_len > 0 && str_len > 0) {

		for (i=0; i < 256; i++) {
			key[i] = pwd[i%pwd_len];
			box[i]=i;
		}

		i=0; j=0;

		for (; i < 256; i++) {
			j = (j + box[i] + key[i]) % 256;
			tmp = box[i];
			box[i] = box[j];
			box[j] = tmp;
		}

		i=0; j=0; a=0;

		for (; i < str_len; i++)	{
			a = (a + 1) % 256;
			j = (j + box[a]) % 256;
			tmp = box[a];
			box[a] = box[j];
			box[j] = tmp;
			k = box[((box[a] + box[j]) % 256)];
			FormatEx(output[2*i], maxlen-2*i, "%02x", input[i] ^ k);
		}
	}
}

/*
 * Encrypts binary data using RC4.
 *
 * @param input			The source data to be encrypted.
 * @param str_len		The length of the source data.
 * @param pwd			The password/key used to encode and decode the data.
 * @param output		The encoded result.
 * @param maxlen		The maximum length of the output buffer.
 * @noreturn
 */
stock Crypt_RC4EncodeBinary(const String:input[], str_len, const String:pwd[], String:output[], maxlen)
{
	decl pwd_len,i,j,a,k;
	decl key[256];
	decl box[256];
	decl tmp;

	pwd_len = strlen(pwd);

	if (pwd_len > 0 && str_len > 0) {

		for(i=0;i<256;i++) {
			key[i] = pwd[i%pwd_len];
			box[i]=i;
		}

		i=0; j=0;

		for (; i < 256; i++) {
			j = (j + box[i] + key[i]) % 256;
			tmp = box[i];
			box[i] = box[j];
			box[j] = tmp;
		}

		i=0; j=0; a=0;

		if (str_len+1 > maxlen) {
			str_len = maxlen - 1;
		}

		for(; i < str_len; i++)	{
			a = (a + 1) % 256;
			j = (j + box[a]) % 256;
			tmp = box[a];
			box[a] = box[j];
			box[j] = tmp;
			k = box[((box[a] + box[j]) % 256)];
			FormatEx(output[i], maxlen-i, "%c", input[i] ^ k);
		}

		/*
		 * i = number of written bits (remember increment occurs at end of for loop, and THEN it fails the loop condition)
		 * Since we're working with binary data, the calling function should not depend on the escape
		 * character, but putting it here prevents crashes in case someone tries to read the data like a string
		 */
		output[i] = '\0';

		return i;
	}
	else {
		return -1;
	}
}
