//! \file       ArcYSK.cs
//! \date       2018 Apr 22
//! \brief      GPlay engine resource archive.
//
// Copyright (C) 2018 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using GameRes.Utility;

namespace GameRes.Formats.GPlay
{
    [Export(typeof(ArchiveFormat))]
    public class YskOpener : ArchiveFormat
    {
        public override string         Tag { get { return "YSK"; } }
        public override string Description { get { return "GPlay engine resource archive"; } }
        public override uint     Signature { get { return 0x36314141; } } // 'AA1640124080'
        public override bool  IsHierarchic { get { return false; } }
        public override bool      CanWrite { get { return false; } }

        public YskOpener ()
        {
            // "AA1640124080", "AA7790743350", "AA1825646340", "AA1316763700", "AA1945074730", "AA7235065580"
            Signatures = new uint[] {
                0x36314141, 0x39324141, 0x37374141, 0x38314141, 0x33314141, 0x39314141, 0x32374141, 0
            };
        }

        const ulong DefaultKey = 0x1234567812345678ul;

        public override ArcFile TryOpen (ArcView file)
        {
            if (!file.View.AsciiEqual (0, "AA"))
                return null;
            int count = file.View.ReadInt32 (12);
            if (!IsSaneCount (count))
                return null;
            var ver_str = file.View.ReadString (2, 10);
            if (!ver_str.All (char.IsDigit))
                return null;

            uint index_offset = 0x10;
            long data_offset = index_offset + count * 0x18;
            var dir = new List<Entry> (count);
            for (int i = 0; i < count; ++i)
            {
                var name = file.View.ReadString (index_offset, 0x14);
                var entry = Create<Entry> (name);
                entry.Offset = data_offset;
                entry.Size = file.View.ReadUInt32 (index_offset+0x14);
                if (!entry.CheckPlacement (file.MaxOffset))
                    return null;
                dir.Add (entry);
                index_offset += 0x18;
                data_offset += entry.Size;
            }
            return new ArcFile (file, this, dir);
        }

        public override Stream OpenEntry (ArcFile arc, Entry entry)
        {
            if (entry.Name.HasAnyOfExtensions ("TXT", "DAT"))
            {
                var input = arc.File.CreateStream (entry.Offset, entry.Size, entry.Name);
                int first_byte = (int)input.Signature & 0xFF;
                if (first_byte == '#' || first_byte == '*')
                    return input;
                var dec = new DesTransform (DefaultKey);
                return new InputCryptoStream (input, dec);
            }
            else if (entry.Name.HasExtension ("JPG"))
            {
                using (var dec = new DesTransform (DefaultKey))
                {
                    var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
                    for (int i = 0; i < data.Length; i += 0x1000)
                    {
                        int count = Math.Min (8, data.Length-i);
                        dec.TransformBlock (data, i, count, data, i);
                    }
                    return new BinMemoryStream (data, entry.Name);
                }
            }
            else if (entry.Name.HasExtension ("BMP"))
            {
                var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
                int bpp = data.ToUInt16 (0x1C);
                if (4 == bpp)
                    data[0x1C] = 8;
                else if (16 == bpp)
                    data[0x1C] = 24;
                int pixels_src = 0x36;
                int pixels_length = data.Length - pixels_src;
                if (pixels_length > 0x493AA)
                {
                    using (var dec = new DesTransform (DefaultKey))
                    {
                        for (int src = pixels_src + 0x493AA; src + 8 <= data.Length; src += 0xA0)
                        {
                            dec.TransformBlock (data, src, 8, data, src);
                        }
                    }
                }
                return new BinMemoryStream (data, entry.Name);
            }
            else
            {
                return base.OpenEntry (arc, entry);
            }
        }
    }

    public class DesTransform : ICryptoTransform
    {
        const int BlockSize = 8;

        public bool          CanReuseTransform { get { return true; } }
        public bool CanTransformMultipleBlocks { get { return true; } }
        public int              InputBlockSize { get { return BlockSize; } }
        public int             OutputBlockSize { get { return BlockSize; } }

        public DesTransform (ulong key)
        {
            SetKey (key);
        }

        byte[]  m_state1 = new byte[512]; // dword_461820
        ulong[] m_state0 = new ulong[16]; // dword_462020

        internal void SetKey (ulong key)
        {
            uint p = 0;
            uint q = 0;
            for (int i = 0; i < 28; ++i)
            {
                p >>= 1;
                q >>= 1;
                if (((key >> pShift0[i]) & 1) != 0)
                    p |= 0x8000000u;
                if (((key >> pShift1[i]) & 1) != 0)
                    q |= 0x8000000u;
            }
            for (int i = 0; i < 16; ++i)
            {
                uint p0 = (p | (p << 28)) >> pShift2[i];
                q = ((q | (p << 28)) >> pShift2[i]) & 0xFFFFFFF;
                p = p0 & 0xFFFFFFF;
                uint q0 = p | (q << 28);
                ulong x = (ulong)(q >> 4) << 32 | q0;
                ulong s = 0;
                for (int j = 0; j < 48; ++j)
                {
                    s >>= 1;
                    if (((x >> pShift3[j]) & 1) != 0)
                        s |= 0x800000000000ul;
                }
                m_state0[i] = s;
            }
            int idx4 = 0;
            for (int i = 0; i < 4; i += 2)
            {
                int idx3 = idx4;
                for (int j = 0; j < 16; )
                {
                    int idx2 = idx3;
                    for (int k = 2; k > 0; --k)
                    {
                        int idx1 = idx2;
                        for (int m = 2; m > 0; --m)
                        {
                            int idx = idx1 / 4;
                            for (int n = 2; n > 0; --n)
                            {
                                int src = j + 16 * i;
                                m_state1[0   + idx] = pState[st0[src]];
                                m_state1[64  + idx] = pState[st1[src]];
                                m_state1[128 + idx] = pState[st2[src]];
                                m_state1[192 + idx] = pState[st3[src]];
                                m_state1[256 + idx] = pState[st4[src]];
                                m_state1[320 + idx] = pState[st5[src]];
                                m_state1[384 + idx] = pState[st6[src]];
                                m_state1[448 + idx] = pState[st7[src]];
                                src = j + 16 * (i + 1);
                                m_state1[32 + idx]  = pState[st0[src]];
                                m_state1[96 + idx]  = pState[st1[src]];
                                m_state1[160 + idx] = pState[st2[src]];
                                m_state1[224 + idx] = pState[st3[src]];
                                m_state1[288 + idx] = pState[st4[src]];
                                m_state1[352 + idx] = pState[st5[src]];
                                m_state1[416 + idx] = pState[st6[src]];
                                m_state1[480 + idx] = pState[st7[src]];
                                idx += 16;
                                ++j;
                            }
                            idx1 += 32;
                        }
                        idx2 += 16;
                    }
                    idx3 += 8;
                }
                idx4 += 4;
            }
        }

        internal ulong TransformQWord (ulong q)
        {
            uint hi = 0;
            uint lo = 0;
            for (int i = 0; i < 32; ++i)
            {
                hi >>= 1;
                lo >>= 1;
                if (((q >> tShift0[i]) & 1) != 0)
                    hi |= 0x80000000;
                if (((q >> tShift1[i]) & 1) != 0)
                    lo |= 0x80000000;
            }
            uint t = lo;
            for (int i = 15; i >= 0; --i)
            {
                ulong val = 0;
                for (int j = 0; j < 48; ++j)
                {
                    val >>= 1;
                    if ((tMask0[j] & lo) != 0)
                        val |= 0x800000000000ul;
                }
                val ^= m_state0[i];
                byte n0 = (byte)val;
                byte n1 = (byte)(val >> 6);
                byte n2 = (byte)(val >> 12);
                byte n3 = (byte)(val >> 18);
                byte n4 = (byte)(val >> 24);
                byte n5 = (byte)(val >> 30);
                byte n6 = (byte)(val >> 36);
                byte n7 = (byte)(val >> 42);
                uint m = (uint)m_state1[(n0 & 0x3F) + 384]
                    | 16u * (m_state1[(n1 & 0x3F) + 64]
                    | 16u * (m_state1[(n2 & 0x3F)]
                    | 16u * (m_state1[(n3 & 0x3F) + 256]
                    | 16u * (m_state1[(n4 & 0x3F) + 128]
                    | 16u * (m_state1[(n5 & 0x3F) + 192]
                    | 16u * (m_state1[(n6 & 0x3F) + 448]
                    | 16u *  m_state1[(n7 & 0x3F) + 320]))))));
                uint x = 0;
                for (int j = 0; j < 32; ++j)
                {
                    x >>= 1;
                    if ((tMask1[j] & m) != 0)
                        x |= 0x80000000;
                }
                t = lo;
                lo = hi ^ x;
                hi = t;
            }
            ulong s = (ulong)t << 32 | lo;
            ulong r = 0;
            for (int j = 0; j < 64; ++j)
            {
                r >>= 1;
                if (((s >> tShift2[j]) & 1) != 0)
                    r |= 0x8000000000000000ul;
            }
            return r;
        }

        public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount,
                                   byte[] outputBuffer, int outputOffset)
        {
            for (int i = 0; i < inputCount; i += 8)
            {
                ulong q = 0;
                int count = Math.Min (8, inputCount - i);
                for (int j = 0; j < count; ++j)
                {
                    q |= (ulong)inputBuffer[inputOffset+j] << (j << 3);
                }
                q = TransformQWord (q);
                for (int j = 0; j < count; ++j)
                {
                    outputBuffer[outputOffset+j] = (byte)q;
                    q >>= 8;
                }
                inputOffset += 8;
                outputOffset += 8;
            }
            return inputCount;
        }

        public byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
        {
            byte[] outputBuffer = new byte[inputCount];
            TransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, 0);
            return outputBuffer;
        }

        public void Dispose ()
        {
        }

        static readonly byte[] pShift0 = {
            56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
            9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35
        };
        static readonly byte[] pShift1 = {
            62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
            13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3
        };
        static readonly byte[] pShift2 = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
        static readonly byte[] pShift3 = {
            13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18,
            11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36,
            46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33,
            52, 45, 41, 49, 35, 28, 31
        };
        static readonly byte[] pState = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
        static readonly byte[] st6 = {
            14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
            0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
            4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
            15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
        };
        static readonly byte[] st1 = {
            15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
            3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
            0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
            13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
        };
        static readonly byte[] st0 = {
            10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
            13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
            13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
            1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
        };
        static readonly byte[] st4 = {
            7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
            13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
            10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
            3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
        };
        static readonly byte[] st2 = {
            2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
            14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
            4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
            11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
        };
        static readonly byte[] st3 = {
            12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
            10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
            9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
            4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
        };
        static readonly byte[] st7 = {
            4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
            13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
            1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
            6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
        };
        static readonly byte[] st5 = {
            13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
            1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
            7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
            2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11,
        };
        static readonly byte[] tShift0 = {
            57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19,
            11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39,
            31, 23, 15, 7
        };
        static readonly byte[] tShift1 = {
            56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18,
            10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38,
            30, 22, 14, 6
        };
        static readonly byte[] tShift2 = {
            39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22,
            62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12,
            52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2,
            42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25,
            32, 0, 40, 8, 48, 16, 56, 24,
        };
        static readonly uint[] tMask0 = {
            0x80000000, 1, 2, 4, 8, 0x10, 8, 0x10, 0x20, 0x40, 0x80, 0x100,
            0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x800, 0x1000, 0x2000,
            0x4000, 0x8000, 0x10000, 0x8000, 0x10000, 0x20000, 0x40000,
            0x80000, 0x100000, 0x80000, 0x100000, 0x200000, 0x400000,
            0x800000, 0x1000000, 0x800000, 0x1000000, 0x2000000, 0x4000000,
            0x8000000, 0x10000000, 0x8000000, 0x10000000, 0x20000000,
            0x40000000, 0x80000000, 1
        };
        static readonly uint[] tMask1 = {
            0x8000, 0x40, 0x80000, 0x100000, 0x10000000, 0x800, 0x8000000,
            0x10000, 1, 0x4000, 0x400000, 0x2000000, 0x10, 0x20000, 0x40000000,
            0x200, 2, 0x80, 0x800000, 0x2000, 0x80000000, 0x4000000, 4,
            0x100, 0x40000, 0x1000, 0x20000000, 0x20, 0x200000, 0x400,
            8, 0x1000000
        };
    }
}
