#region Copyright & License Information
/*
 * Copyright 2007,2009,2010 Chris Forbes, Robert Pepperell, Matthew Bowra-Dean, Paul Chote, Alli Witheford.
 * This file is part of OpenRA.
 * 
 *  OpenRA is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  OpenRA is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with OpenRA.  If not, see <http://www.gnu.org/licenses/>.
 */
#endregion

using System;
using System.Linq;

namespace OpenRA.FileFormats
{
    /* possibly the fugliest C# i've ever seen. */

    class BlowfishKeyProvider
    {
        const string pubkeyStr = "AihRvNoIbTn85FZRYNZRcT+i6KpU+maCsEqr3Q5q+LDB5tH7Tz2qQ38V";

        static sbyte[] char2num = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};

        class PublicKey
        {
            public uint[] key1 = new uint[64];
            public uint[] key2 = new uint[64];
            public uint len;
        }
        PublicKey pubkey = new PublicKey();

        uint[] glob1 = new uint[64];
        uint glob1_bitlen, glob1_len_x2;
        uint[] glob2 = new uint[130];
        uint[] glob1_hi = new uint[4];
        uint[] glob1_hi_inv = new uint[4];
        uint glob1_hi_bitlen;
        uint glob1_hi_inv_lo, glob1_hi_inv_hi;

        void init_bignum(uint[] n, uint val, uint len)
        {
            for (int i = 0; i < len; i++) n[i] = 0;
            n[0] = val;
        }

        void move_key_to_big(uint[] n, byte[] key, uint klen, uint blen)
        {
            byte sign;

            if ((key[0] & 0x80) != 0) sign = 0xff;
            else sign = 0;

            unsafe
            {
                fixed (uint* _pn = &n[0])
                {
                    byte* pn = (byte*)_pn;
                    uint i = blen * 4;
                    for (; i > klen; i--) pn[i - 1] = (byte)sign;
                    for (; i > 0; i--) pn[i - 1] = key[klen - i];
                }
            }
        }

        void key_to_bignum(uint[] n, byte[] key, uint len)
        {
            uint keylen;
            int i;

            int j = 0;

            if (key[j] != 2) return;
            j++;

            if ((key[j] & 0x80) != 0)
            {
                keylen = 0;
                for (i = 0; i < (key[j] & 0x7f); i++) keylen = (keylen << 8) | key[j + i + 1];
                j += (key[j] & 0x7f) + 1;
            }
            else
            {
                keylen = key[j];
                j++;
            }
            if (keylen <= len * 4)
                move_key_to_big(n, key.Skip(j).ToArray(), keylen, len);
        }

        uint len_bignum(uint[] n, uint len)
        {
            uint i;
            i = len - 1;
            while ((i >= 0) && (n[i] == 0)) i--;
            return i + 1;
        }

        uint bitlen_bignum(uint[] n, uint len)
        {
            uint ddlen, bitlen, mask;
            ddlen = len_bignum(n, len);
            if (ddlen == 0) return 0;
            bitlen = ddlen * 32;
            mask = 0x80000000;
            while ((mask & n[ddlen - 1]) == 0)
            {
                mask >>= 1;
                bitlen--;
            }
            return bitlen;
        }

        void init_pubkey()
        {
            int i = 0;
            uint i2, tmp;
            byte[] keytmp = new byte[256];

            init_bignum(pubkey.key2, 0x10001, 64);

            i2 = 0;
            while (i < pubkeyStr.Length)
            {
                tmp = (uint)char2num[pubkeyStr[i++]];
                tmp <<= 6; tmp |= (uint)(byte)char2num[pubkeyStr[i++]];
                tmp <<= 6; tmp |= (uint)(byte)char2num[pubkeyStr[i++]];
                tmp <<= 6; tmp |= (uint)(byte)char2num[pubkeyStr[i++]];
                keytmp[i2++] = (byte)((tmp >> 16) & 0xff);
                keytmp[i2++] = (byte)((tmp >> 8) & 0xff);
                keytmp[i2++] = (byte)(tmp & 0xff);
            }

            key_to_bignum(pubkey.key1, keytmp, 64);
            pubkey.len = bitlen_bignum(pubkey.key1, 64) - 1;
        }

        uint len_predata()
        {
            uint a = (pubkey.len - 1) / 8;
            return (55 / a + 1) * (a + 1);
        }

        int cmp_bignum(uint[] n1, uint[] n2, uint len)
        {

            while (len > 0)
            {
                --len;
                if (n1[len] < n2[len]) return -1;
                if (n1[len] > n2[len]) return 1;
            }
            return 0;
        }

        void mov_bignum(uint[] dest, uint[] src, uint len)
        {
            Array.Copy(src, dest, len);
        }

        void shr_bignum(uint[] n, int bits, int len)
        {
            int i; int i2 = bits / 32;

            if (i2 > 0)
            {
                for (i = 0; i < len - i2; i++) n[i] = n[i + i2];
                for (; i < len; i++) n[i] = 0;
                bits = bits % 32;
            }
            if (bits == 0) return;
            for (i = 0; i < len - 1; i++) n[i] = (n[i] >> bits) | (n[i + 1] << (32 -
          bits));
            n[i] = n[i] >> bits;
        }

        void shl_bignum(uint[] n, int bits, int len)
        {
            int i, i2;

            i2 = bits / 32;
            if (i2 > 0)
            {
                for (i = len - 1; i > i2; i--) n[i] = n[i - i2];
                for (; i > 0; i--) n[i] = 0;
                bits = bits % 32;
            }
            if (bits == 0) return;
            for (i = len - 1; i > 0; i--) n[i] = (n[i] << bits) | (n[i - 1] >> (32 -
          bits));
            n[0] <<= bits;
        }

        uint sub_bignum(uint[] dest, uint[] src1, uint[] src2, uint carry, int len)
        {
            uint i1, i2;

            len += len;
            unsafe
            {
                fixed (uint* _ps1 = &src1[0])
                fixed (uint* _ps2 = &src2[0])
                fixed (uint* _pd = &dest[0])
                {
                    ushort* ps1 = (ushort*)_ps1;
                    ushort* ps2 = (ushort*)_ps2;
                    ushort* pd = (ushort*)_pd;

                    while (--len != -1)
                    {
                        i1 = *ps1++;
                        i2 = *ps2++;
                        *pd++ = (ushort)(i1 - i2 - carry);
                        if (((i1 - i2 - carry) & 0x10000) != 0) carry = 1; else carry = 0;
                    }
                }
            }
            return carry;
        }

        unsafe uint sub_bignum(uint* dest, uint* src1, uint* src2, uint carry, int len)
        {
            uint i1, i2;

            len += len;

            ushort* ps1 = (ushort*)src1;
            ushort* ps2 = (ushort*)src2;
            ushort* pd = (ushort*)dest;

            while (--len != -1)
            {
                i1 = *ps1++;
                i2 = *ps2++;
                *pd++ = (ushort)(i1 - i2 - carry);
                if (((i1 - i2 - carry) & 0x10000) != 0) carry = 1; else carry = 0;

            }
            return carry;
        }

        void inv_bignum(uint[] n1, uint[] n2, uint len)
        {
            uint[] n_tmp = new uint[64];
            uint n2_bytelen, bit;
            int n2_bitlen;

            int j = 0;

            init_bignum(n_tmp, 0, len);
            init_bignum(n1, 0, len);
            n2_bitlen = (int)bitlen_bignum(n2, len);
            bit = ((uint)1) << (n2_bitlen % 32);
            j = ((n2_bitlen + 32) / 32) - 1;
            n2_bytelen = (uint)((n2_bitlen - 1) / 32) * 4;
            n_tmp[n2_bytelen / 4] |= ((uint)1) << ((n2_bitlen - 1) & 0x1f);

            while (n2_bitlen > 0)
            {
                n2_bitlen--;
                shl_bignum(n_tmp, 1, (int)len);
                if (cmp_bignum(n_tmp, n2, len) != -1)
                {
                    sub_bignum(n_tmp, n_tmp, n2, 0, (int)len);
                    n1[j] |= bit;
                }
                bit >>= 1;
                if (bit == 0)
                {
                    j--;
                    bit = 0x80000000;
                }
            }
            init_bignum(n_tmp, 0, len);
        }

        void inc_bignum(uint[] n, uint len)
        {
            int i = 0;
            while ((++n[i] == 0) && (--len > 0)) i++;
        }

        void init_two_dw(uint[] n, uint len)
        {
            mov_bignum(glob1, n, len);
            glob1_bitlen = bitlen_bignum(glob1, len);
            glob1_len_x2 = (glob1_bitlen + 15) / 16;
            mov_bignum(glob1_hi, glob1.Skip((int)len_bignum(glob1, len) - 2).ToArray(), 2);
            glob1_hi_bitlen = bitlen_bignum(glob1_hi, 2) - 32;
            shr_bignum(glob1_hi, (int)glob1_hi_bitlen, 2);
            inv_bignum(glob1_hi_inv, glob1_hi, 2);
            shr_bignum(glob1_hi_inv, 1, 2);
            glob1_hi_bitlen = (glob1_hi_bitlen + 15) % 16 + 1;
            inc_bignum(glob1_hi_inv, 2);
            if (bitlen_bignum(glob1_hi_inv, 2) > 32)
            {
                shr_bignum(glob1_hi_inv, 1, 2);
                glob1_hi_bitlen--;
            }
            glob1_hi_inv_lo = (ushort)glob1_hi_inv[0];
            glob1_hi_inv_hi = (ushort)(glob1_hi_inv[0] >> 16);
        }

        unsafe void mul_bignum_word(ushort *pn1, uint[] n2, uint mul, uint len)
        {
            uint i, tmp;
            unsafe
            {
                fixed (uint* _pn2 = &n2[0])
                {
                    ushort* pn2 = (ushort*)_pn2;

                    tmp = 0;
                    for (i = 0; i < len; i++)
                    {
                        tmp = mul * (*pn2) + (*pn1) + tmp;
                        *pn1 = (ushort)tmp;
                        pn1++;
                        pn2++;
                        tmp >>= 16;
                    }
                    *pn1 += (ushort)tmp;
                }
            }
        }

        void mul_bignum(uint[] dest, uint[] src1, uint[] src2, uint len)
        {
          uint i;

          unsafe
          {
              fixed( uint * _psrc2 = &src2[0] )
              fixed(uint* _pdest = &dest[0])
              {
                  ushort* psrc2 = (ushort*)_psrc2;
                  ushort* pdest = (ushort*)_pdest;

                  init_bignum(dest, 0, len * 2);
                  for (i = 0; i < len * 2; i++)
                      mul_bignum_word(pdest++, src1, *psrc2++, len * 2);
              }
          }
        }

        void not_bignum(uint[] n, uint len)
        {
          uint i;
          for (i = 0; i < len; i++) n[i] = ~n[i];
        }

        void neg_bignum(uint[] n, uint len)
        {
          not_bignum(n, len);
          inc_bignum(n, len);
        }

        unsafe uint get_mulword(uint* n)
        {
            ushort* wn = (ushort*)n;
            uint i = (uint)((((((((((*(wn - 1) ^ 0xffff) & 0xffff) * glob1_hi_inv_lo + 0x10000) >> 1)
                 + (((*(wn - 2) ^ 0xffff) * glob1_hi_inv_hi + glob1_hi_inv_hi) >> 1) + 1)
                 >> 16) + ((((*(wn - 1) ^ 0xffff) & 0xffff) * glob1_hi_inv_hi) >> 1) +
                 (((*wn ^ 0xffff) * glob1_hi_inv_lo) >> 1) + 1) >> 14) + glob1_hi_inv_hi
                 * (*wn ^ 0xffff) * 2) >> (int)glob1_hi_bitlen);
            if (i > 0xffff) i = 0xffff;
            return i & 0xffff;
        }

        void dec_bignum(uint[] n, uint len)
        {
            int i = 0;
            while ((--n[i] == 0xffffffff) && (--len > 0)) 
                i++;
        }

        void calc_a_bignum(uint[] n1, uint[] n2, uint[] n3, uint len)
        {
            uint g2_len_x2, len_diff;
            unsafe
            {
                fixed( uint* g1 = &glob1[0])
                fixed (uint* g2 = &glob2[0])
                {
                    mul_bignum(glob2, n2, n3, len);
                    glob2[len * 2] = 0;
                    g2_len_x2 = len_bignum(glob2, len * 2 + 1) * 2;
                    if (g2_len_x2 >= glob1_len_x2)
                    {
                        inc_bignum(glob2, len * 2 + 1);
                        neg_bignum(glob2, len * 2 + 1);
                        len_diff = g2_len_x2 + 1 - glob1_len_x2;
                        ushort* esi = ((ushort*)g2) + (1 + g2_len_x2 - glob1_len_x2);
                        ushort* edi = ((ushort*)g2) + (g2_len_x2 + 1);
                        for (; len_diff != 0; len_diff--)
                        {
                            edi--;
                            uint tmp = get_mulword((uint*)edi);
                            esi--;
                            if (tmp > 0)
                            {
                                mul_bignum_word(esi, glob1, tmp, 2 * len);
                                if ((*edi & 0x8000) == 0)
                                {
                                    if (0 != sub_bignum((uint*)esi, (uint*)esi, g1, 0, (int)len)) (*edi)--;
                                }
                            }
                        }
                        neg_bignum(glob2, len);
                        dec_bignum(glob2, len);
                    }
                    mov_bignum(n1, glob2, len);
                }
            }
        }

        void clear_tmp_vars(uint len)
        {
            init_bignum(glob1, 0, len);
            init_bignum(glob2, 0, len);
            init_bignum(glob1_hi_inv, 0, 4);
            init_bignum(glob1_hi, 0, 4);
            glob1_bitlen = 0;
            glob1_hi_bitlen = 0;
            glob1_len_x2 = 0;
            glob1_hi_inv_lo = 0;
            glob1_hi_inv_hi = 0;
        }

        void calc_a_key(uint[] n1, uint[] n2, uint[] n3, uint[] n4, uint len)
        {
            uint[] n_tmp = new uint[64];
            uint n3_len, n4_len;
            int n3_bitlen;
            uint bit_mask;

            unsafe
            {
                fixed (uint* _pn3 = &n3[0])
                {
                    uint* pn3 = _pn3;

                    init_bignum(n1, 1, len);
                    n4_len = len_bignum(n4, len);
                    init_two_dw(n4, n4_len);
                    n3_bitlen = (int)bitlen_bignum(n3, n4_len);
                    n3_len = (uint)((n3_bitlen + 31) / 32);
                    bit_mask = (((uint)1) << ((n3_bitlen - 1) % 32)) >> 1;
                    pn3 += n3_len - 1;
                    n3_bitlen--;
                    mov_bignum(n1, n2, n4_len);
                    while (--n3_bitlen != -1)
                    {
                        if (bit_mask == 0)
                        {
                            bit_mask = 0x80000000;
                            pn3--;
                        }
                        calc_a_bignum(n_tmp, n1, n1, n4_len);
                        if ((*pn3 & bit_mask) != 0)
                            calc_a_bignum(n1, n_tmp, n2, n4_len);
                        else
                            mov_bignum(n1, n_tmp, n4_len);
                        bit_mask >>= 1;
                    }
                    init_bignum(n_tmp, 0, n4_len);
                    clear_tmp_vars(len);
                }
            }
        }

        unsafe void memcpy(byte* dest, byte* src, int len)
        {
            while (len-- != 0) *dest++ = *src++;
        }

        unsafe void process_predata(byte* pre, uint pre_len, byte *buf)
        {
            uint[] n2 = new uint[64];
            uint[] n3 = new uint[64];

            uint a = (pubkey.len - 1) / 8;
            while (a + 1 <= pre_len) 
            {
                init_bignum(n2, 0, 64);
                fixed( uint * pn2 = &n2[0] )
                    memcpy((byte *)pn2, pre, (int)a + 1);
                calc_a_key(n3, n2, pubkey.key2, pubkey.key1, 64);

                fixed( uint * pn3 = &n3[0] )
                    memcpy(buf, (byte *)pn3, (int)a);

                pre_len -= a + 1;
                pre += a + 1;
                buf += a;
            }
        }

        public byte[] DecryptKey(byte[] src)
        {
            init_pubkey();
            byte[] dest = new byte[256];

            unsafe
            {
                fixed (byte* pdest = &dest[0])
                fixed (byte* psrc = &src[0])
                    process_predata(psrc, len_predata(), pdest);
            }
            return dest.Take(56).ToArray();
        }
    }
}
