using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace PspCrypto
{
    public static class KIRKEngine
    {
        // KIRK return values
        public const int KIRK_OPERATION_SUCCESS = 0;
        public const int KIRK_NOT_ENABLED = 1;
        public const int KIRK_INVALID_MODE = 2;
        public const int KIRK_HEADER_HASH_INVALID = 3;
        public const int KIRK_DATA_HASH_INVALID = 4;
        public const int KIRK_SIG_CHECK_INVALID = 5;
        public const int KIRK_UNK_1 = 6;
        public const int KIRK_UNK_2 = 7;
        public const int KIRK_UNK_3 = 8;
        public const int KIRK_UNK_4 = 9;
        public const int KIRK_UNK_5 = 0xA;
        public const int KIRK_UNK_6 = 0xB;
        public const int KIRK_NOT_INITIALIZED = 0xC;
        public const int KIRK_INVALID_OPERATION = 0xD;
        public const int KIRK_INVALID_SEED_CODE = 0xE;
        public const int KIRK_INVALID_SIZE = 0xF;
        public const int KIRK_DATA_SIZE_ZERO = 0x10;

        // sceUtilsBufferCopyWithRange modes
        public const int KIRK_CMD_DECRYPT_PRIVATE = 1;
        public const int KIRK_CMD_2 = 2;
        public const int KIRK_CMD_3 = 3;
        public const int KIRK_CMD_ENCRYPT_IV_0 = 4;
        public const int KIRK_CMD_ENCRYPT_IV_FUSE = 5;
        public const int KIRK_CMD_ENCRYPT_IV_USER = 6;
        public const int KIRK_CMD_DECRYPT_IV_0 = 7;
        public const int KIRK_CMD_DECRYPT_IV_FUSE = 8;
        public const int KIRK_CMD_DECRYPT_IV_USER = 9;
        public const int KIRK_CMD_PRIV_SIGN_CHECK = 10;
        public const int KIRK_CMD_SHA1_HASH = 11;
        public const int KIRK_CMD_ECDSA_GEN_KEYS = 12;
        public const int KIRK_CMD_ECDSA_MULTIPLY_POINT = 13;
        public const int KIRK_CMD_PRNG = 14;
        public const int KIRK_CMD_15 = 15;
        public const int KIRK_CMD_ECDSA_SIGN = 16;
        public const int KIRK_CMD_ECDSA_VERIFY = 17;

        // KIRK header modes
        public const int KIRK_MODE_CMD1 = 1;
        public const int KIRK_MODE_CMD2 = 2;
        public const int KIRK_MODE_CMD3 = 3;
        public const int KIRK_MODE_ENCRYPT_CBC = 4;
        public const int KIRK_MODE_DECRYPT_CBC = 5;

        // sceUtilsBufferCopyWithRange errors
        public const int SUBCWR_NOT_16_ALGINED = 0x90A;
        public const int SUBCWR_HEADER_HASH_INVALID = 0x920;
        public const int SUBCWR_BUFFER_TOO_SMALL = 0x1000;

        [StructLayout(LayoutKind.Sequential)]
        public struct KIRK_AES128CBC_HEADER
        {
            public int mode;
            public int unk_4;
            public int unk_8;
            public int keyseed;
            public int data_size;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public unsafe struct KIRK_CMD1_HEADER
        {
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            private fixed byte _AES_key[16];
            public Span<byte> AES_Key
            {
                get
                {
                    fixed (byte* ptr = _AES_key)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }
            public Span<byte> AESKeys
            {
                get
                {
                    fixed (byte* ptr = _AES_key)
                    {
                        return new Span<byte>(ptr, 32);
                    }
                }
            }

            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            private fixed byte _CMAC_key[16]; // 0x10
            public Span<byte> CMAC_key
            {
                get
                {
                    fixed (byte* ptr = _CMAC_key)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }

            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            private fixed byte _CMAC_header_hash[16]; // 0x20
            public Span<byte> CMAC_header_hash
            {
                get
                {
                    fixed (byte* ptr = _CMAC_header_hash)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            private fixed byte _CMAC_data_hash[16]; // 0x30
            public Span<byte> CMAC_data_hash
            {
                get
                {
                    fixed (byte* ptr = _CMAC_data_hash)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }

            public Span<byte> content
            {
                get
                {
                    fixed (byte* ptr = unused)
                    {
                        return new Span<byte>(ptr, 0x40);
                    }
                }
            }
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            fixed byte unused[32]; // 0x40
            public uint mode; // 0x60
            public byte ecdsa_hash; // 0x64
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
            fixed byte unk3[11]; // 0x65
            public int data_size; // 0x70

            public Span<byte> off70
            {
                get
                {
                    fixed (int* ptr = &data_size)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public int data_offset; // 0x74
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            fixed byte unk4[8]; // 0x78
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            fixed byte unk5[16]; // 0x80
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct KIRK_CMD1_ECDSA_HEADER
        {
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            private fixed byte _AES_key[16];

            public Span<byte> AES_Key
            {
                get
                {
                    fixed (byte* ptr = _AES_key)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }

            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            private fixed byte _header_sig_r[20]; // 0x10

            public Span<byte> header_sig_r
            {
                get
                {
                    fixed (byte* ptr = _header_sig_r)
                    {
                        return new Span<byte>(ptr, 20);
                    }
                }
            }
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            private fixed byte _header_sig_s[20]; // 0x24

            public Span<byte> header_sig_s
            {
                get
                {
                    fixed (byte* ptr = _header_sig_s)
                    {
                        return new Span<byte>(ptr, 20);
                    }
                }
            }

            public Span<byte> header_sig
            {
                get
                {
                    fixed (byte* ptr = _header_sig_r)
                    {
                        return new Span<byte>(ptr, 40);
                    }
                }
            }


            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            private fixed byte _data_sig_r[20];  // 0x38

            public Span<byte> data_sig_r
            {
                get
                {
                    fixed (byte* ptr = _data_sig_r)
                    {
                        return new Span<byte>(ptr, 20);
                    }
                }
            }
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            private fixed byte _data_sig_s[20]; // 0x4c
            public Span<byte> data_sig_s
            {
                get
                {
                    fixed (byte* ptr = _data_sig_s)
                    {
                        return new Span<byte>(ptr, 20);
                    }
                }
            }

            public Span<byte> data_sig
            {
                get
                {
                    fixed (byte* ptr = _data_sig_r)
                    {
                        return new Span<byte>(ptr, 40);
                    }
                }
            }


            public uint mode; //0x60
            public byte ecdsa_hash;
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
            fixed byte unk3[11];
            uint data_size;
            uint data_offset;
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            fixed byte unk4[8];
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            fixed byte unk5[16];
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct ECDSA_SIG
        {
            private fixed byte _r[0x14];

            public Span<byte> r
            {
                get
                {
                    fixed (byte* ptr = _r)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }
            private fixed byte _s[0x14];

            public Span<byte> s
            {
                get
                {
                    fixed (byte* ptr = _s)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }

            public Span<byte> sig
            {
                get
                {
                    fixed (byte* ptr = _r)
                    {
                        return new Span<byte>(ptr, 0x28);
                    }
                }
            }

        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct ECDSA_POINT
        {
            private fixed byte _x[0x14];

            public Span<byte> x
            {
                get
                {
                    fixed (byte* ptr = _x)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }
            private fixed byte _y[0x14];


            public Span<byte> y
            {
                get
                {
                    fixed (byte* ptr = _y)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }

            public Span<byte> point
            {
                get
                {
                    fixed (byte* ptr = _x)
                    {
                        return new Span<byte>(ptr, 0x28);
                    }
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KIRK_SHA1_HEADER
        {
            public int data_size;
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct KIRK_CMD12_BUFFER
        {
            private fixed byte private_key_[0x14];

            public Span<byte> private_key
            {
                get
                {
                    fixed (byte* ptr = private_key_)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }
            public ECDSA_POINT public_key;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KIRK_CMD13_BUFFER
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x14)]
            public byte[] multiplier;
            public ECDSA_POINT public_key;
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct KIRK_CMD16_BUFFER
        {
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
            private fixed byte enc_private_[0x20];

            public Span<byte> enc_private
            {
                get
                {
                    fixed (byte* ptr = enc_private_)
                    {
                        return new Span<byte>(ptr, 0x20);
                    }
                }
            }
            // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x14)]
            private fixed byte message_hash_[0x14];

            public Span<byte> message_hash
            {
                get
                {
                    fixed (byte* ptr = message_hash_)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct KIRK_CMD17_BUFFER
        {
            public ECDSA_POINT public_key;
            private fixed byte _message_hash[0x14];

            public Span<byte> message_hash
            {
                get
                {
                    fixed (byte* ptr = _message_hash)
                    {
                        return new Span<byte>(ptr, 0x14);
                    }
                }
            }
            public ECDSA_SIG signature;
        }
        // KIRK commands
        /*
            // Private Sig + Cipher
            0x01: Super-Duper decryption (no inverse)
            0x02: Encrypt Operation (inverse of 0x03)
            0x03: Decrypt Operation (inverse of 0x02)

            // Cipher
            0x04: Encrypt Operation (inverse of 0x07) (IV=0)
            0x05: Encrypt Operation (inverse of 0x08) (IV=FuseID)
            0x06: Encrypt Operation (inverse of 0x09) (IV=UserDefined)
            0x07: Decrypt Operation (inverse of 0x04)
            0x08: Decrypt Operation (inverse of 0x05)
            0x09: Decrypt Operation (inverse of 0x06)

            // Sig Gens
            0x0A: Private Signature Check (checks for private SCE sig)
            0x0B: SHA1 Hash
            0x0C: Mul1
            0x0D: Mul2
            0x0E: Random Number Gen
            0x0F: (absolutely no idea  could be KIRK initialization)
            0x10: Signature Gen

            // Sig Checks
            0x11: Signature Check (checks for generated sigs)
            0x12: Certificate Check (idstorage signatures)
        */

        // Internal variables
        [StructLayout(LayoutKind.Sequential)]
        private unsafe struct kirk16_data
        {
            private fixed byte fuseid_[8];

            public Span<byte> fuseid
            {
                get
                {
                    fixed (byte* ptr = fuseid_)
                    {
                        return new Span<byte>(ptr, 8);
                    }
                }
            }
            private fixed byte mesh_[0x40];

            public Span<byte> mesh
            {
                get
                {
                    fixed (byte* ptr = mesh_)
                    {
                        return new Span<byte>(ptr, 0x40);
                    }
                }
            }
        }

        //[StructLayout(LayoutKind.Sequential)]
        //private unsafe struct header_keys
        //{
        //    // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        //    public fixed byte AES[16];
        //    // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        //    public fixed byte CMAC[16];
        //}

        private static uint g_fuse90;
        private static uint g_fuse94;

        private static Aes aes_kirk1;
        private static byte[] PRNG_DATA = new byte[0x14];

        private static bool is_kirk_initialized;
        private static readonly byte[] IV0 = new byte[16];

        // Internal functions
        private static byte[] kirk_4_7_get_key(int key_type)
        {
            if ((key_type < 0) || (key_type >= 0x80)) throw new Exception("KIRK_INVALID_SIZE");
            return KeyVault.kirkKeys[key_type];
        }

        private static Aes CreateAes()
        {
            var aes_ctx = Aes.Create();
            aes_ctx.Mode = CipherMode.CBC;
            aes_ctx.Padding = PaddingMode.None;
            aes_ctx.IV = IV0;
            return aes_ctx;
        }

        public static void decrypt_kirk16_private(Span<byte> dA_out, Span<byte> dA_enc)
        {
            int i, k;
            kirk16_data keydata;
            byte[] subkey_1 = new byte[0x10], subkey_2 = new byte[0x10];
            using var aes_ctx = CreateAes();

            keydata.fuseid[7] = (byte)(g_fuse90 & 0xFF);
            keydata.fuseid[6] = (byte)((g_fuse90 >> 8) & 0xFF);
            keydata.fuseid[5] = (byte)((g_fuse90 >> 16) & 0xFF);
            keydata.fuseid[4] = (byte)((g_fuse90 >> 24) & 0xFF);
            keydata.fuseid[3] = (byte)(g_fuse94 & 0xFF);
            keydata.fuseid[2] = (byte)((g_fuse94 >> 8) & 0xFF);
            keydata.fuseid[1] = (byte)((g_fuse94 >> 16) & 0xFF);
            keydata.fuseid[0] = (byte)((g_fuse94 >> 24) & 0xFF);

            /* set encryption key */
            aes_ctx.Key = KeyVault.kirk16_key;

            /* set the subkeys */
            for (i = 0; i < 0x10; i++)
            {
                /* set to the fuseid */
                subkey_2[i] = subkey_1[i] = keydata.fuseid[i % 8];
            }
            /* do aes crypto */
            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                using var decryptor = aes_ctx.CreateDecryptor();
                for (i = 0; i < 3; i++)
                {
                    /* encrypt + decrypt */
                    encryptor.TransformBlock(subkey_1, 0, subkey_1.Length, subkey_1, 0);
                    decryptor.TransformBlock(subkey_2, 0, subkey_2.Length, subkey_2, 0);
                }
            }

            /* set new key */
            aes_ctx.Key = subkey_1;
            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                /* now lets make the key mesh */
                for (i = 0; i < 3; i++)
                {
                    /* do encryption in group of 3 */
                    for (k = 0; k < 3; k++)
                    {
                        /* crypto */
                        encryptor.TransformBlock(subkey_2, 0, subkey_2.Length, subkey_2, 0);
                    }

                    /* copy to out block */
                    subkey_2.AsSpan().CopyTo(keydata.mesh[(i * 0x10)..]);
                }
            }

            /* set the key to the mesh */
            aes_ctx.Key = keydata.mesh.Slice(0x20, 0x10).ToArray();

            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                /* do the encryption routines for the aes key */
                for (i = 0; i < 2; i++)
                {
                    /* encrypt the data */
                    var tmp = encryptor.TransformFinalBlock(keydata.mesh.ToArray(), 0x10, 0x30);
                    tmp.CopyTo(keydata.mesh[0x10..]);
                }
            }

            /* set the key to that mesh shit */
            using var aes = CreateAes();
            aes.Key = keydata.mesh.Slice(0x10, 0x10).ToArray();


            /* cbc decrypt the dA */
            AesHelper.AesDecrypt(aes,  dA_out, dA_enc, 0x20);
        }

        public static void encrypt_kirk16_private(Span<byte> dA_out, Span<byte> dA_dec)
        {
            int i, k;
            kirk16_data keydata;
            byte[] subkey_1 = new byte[0x10], subkey_2 = new byte[0x10];
            using var aes_ctx = CreateAes();

            keydata.fuseid[7] = (byte)(g_fuse90 & 0xFF);
            keydata.fuseid[6] = (byte)((g_fuse90 >> 8) & 0xFF);
            keydata.fuseid[5] = (byte)((g_fuse90 >> 16) & 0xFF);
            keydata.fuseid[4] = (byte)((g_fuse90 >> 24) & 0xFF);
            keydata.fuseid[3] = (byte)(g_fuse94 & 0xFF);
            keydata.fuseid[2] = (byte)((g_fuse94 >> 8) & 0xFF);
            keydata.fuseid[1] = (byte)((g_fuse94 >> 16) & 0xFF);
            keydata.fuseid[0] = (byte)((g_fuse94 >> 24) & 0xFF);

            /* set encryption key */
            aes_ctx.Key = KeyVault.kirk16_key;

            /* set the subkeys */
            for (i = 0; i < 0x10; i++)
            {
                /* set to the fuseid */
                subkey_2[i] = subkey_1[i] = keydata.fuseid[i % 8];
            }
            /* do aes crypto */
            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                using var decryptor = aes_ctx.CreateDecryptor();
                for (i = 0; i < 3; i++)
                {
                    /* encrypt + decrypt */
                    subkey_1 = encryptor.TransformFinalBlock(subkey_1, 0, subkey_1.Length);
                    subkey_2 = decryptor.TransformFinalBlock(subkey_2, 0, subkey_2.Length);
                }
            }

            /* set new key */
            aes_ctx.Key = subkey_1;
            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                /* now lets make the key mesh */
                for (i = 0; i < 3; i++)
                {
                    /* do encryption in group of 3 */
                    for (k = 0; k < 3; k++)
                    {
                        /* crypto */
                        encryptor.TransformBlock(subkey_2, 0, subkey_2.Length, subkey_2, 0);
                    }

                    /* copy to out block */
                    subkey_2.AsSpan().CopyTo(keydata.mesh.Slice(i * 0x10));
                }
            }

            /* set the key to the mesh */
            aes_ctx.Key = keydata.mesh.Slice(0x20, 0x10).ToArray();

            using (var encryptor = aes_ctx.CreateEncryptor())
            {
                /* do the encryption routines for the aes key */
                for (i = 0; i < 2; i++)
                {
                    /* encrypt the data */
                    var tmp = encryptor.TransformFinalBlock(keydata.mesh.ToArray(), 0x10, 0x30);
                    tmp.CopyTo(keydata.mesh.Slice(0x10));
                }
            }

            /* set the key to that mesh shit */
            using var aes = CreateAes();
            aes.Key = keydata.mesh.Slice(0x10, 0x10).ToArray();


            /* cbc encrypt the dA */
            AesHelper.AesEncrypt(aes, dA_out, dA_dec, 0x20);
        }

        static KIRKEngine()
        {
            kirk_init();
        }

        // KIRK commands

        public static int kirk_init()
        {
            //94 90
            //0x0000332 e6e050311 3000
            //0x0000772 ccda50f12 2000
            //0x1008010 1ef1e7101 vita
            return kirk_init2(Encoding.ASCII.GetBytes("Lazy Dev should have initialized!"), 33, 0xBABEF00D, 0xDEADBEEF);
        }

        public static int kirk_init2(byte[] rnd_seed, int seed_size, uint fuseid_90, uint fuseid_94)
        {
            Span<byte> temp = stackalloc byte[0x104];

            // Another randomly selected data for a "key" to add to each randomization
            ReadOnlySpan<byte> key = stackalloc byte[] { 0x07, 0xAB, 0xEF, 0xF8, 0x96, 0x8C, 0xF3, 0xD6, 0x14, 0xE0, 0xEB, 0xB2, 0x9D, 0x8B, 0x4E, 0x74 };
            uint curtime;

            is_kirk_initialized = true;

            //Set PRNG_DATA initially, otherwise use what ever uninitialized data is in the buffer
            if (seed_size > 0)
            {
                Span<byte> seedbuf = stackalloc byte[seed_size + 4];
                RandomNumberGenerator.Fill(seedbuf);
                var seedheader = new KIRK_SHA1_HEADER
                {
                    data_size = seed_size
                };
                MemoryMarshal.Write(seedbuf, ref seedheader);
                kirk_CMD11(PRNG_DATA, seedbuf, seed_size + 4);
            }
            // Buffer.BlockCopy(PRNG_DATA, 0, header.data, 0, 0x14);
            PRNG_DATA.CopyTo(temp.Slice(4));

            // This uses the standard C time function for portability.
            curtime = (uint)DateTimeOffset.Now.ToUnixTimeMilliseconds();
            temp[0x18] = (byte)(curtime & 0xFF);
            temp[0x19] = (byte)((curtime >> 8) & 0xFF);
            temp[0x1A] = (byte)((curtime >> 16) & 0xFF);
            temp[0x1B] = (byte)((curtime >> 24) & 0xFF);
            // Buffer.BlockCopy(key, 0, header.data, 0x18, 0x10);
            key.CopyTo(temp.Slice(0x1c));

            // This leaves the remainder of the 0x100 bytes in temp to whatever remains on the stack 
            // in an uninitialized state. This should add unpredicableness to the results as well

            var header = new KIRK_SHA1_HEADER
            {
                data_size = 0x100
            };
            MemoryMarshal.Write(temp, ref header);
            kirk_CMD11(PRNG_DATA, temp, 0x104);


            //Set Fuse ID
            g_fuse90 = fuseid_90;
            g_fuse94 = fuseid_94;

            // Set KIRK1 main key
            aes_kirk1 = CreateAes();
            aes_kirk1.Key = KeyVault.kirk1_key;
            return 0;
        }

        static int kirk_CMD0(Span<byte> outbuff, ReadOnlySpan<byte> inbuff, int size, bool generate_trash)
        {
            KIRK_CMD1_HEADER header = MemoryMarshal.AsRef<KIRK_CMD1_HEADER>(outbuff);
            // header_keys keys = Utils.AsRef<header_keys>(outbuff);
            int chk_size;
            Aes k1;
            Aes cmac_key;

            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;

            inbuff[..size].CopyTo(outbuff);

            if (header.mode != KIRK_MODE_CMD1) return KIRK_INVALID_MODE;

            // FILL PREDATA WITH RANDOM DATA
            if (generate_trash) kirk_CMD14(outbuff[Unsafe.SizeOf<KIRK_CMD1_HEADER>()..], header.data_offset);

            // Make sure data is 16 aligned
            chk_size = header.data_size;
            if (chk_size % 16 != 0) chk_size += 16 - (chk_size % 16);

            // ENCRYPT DATA

            // ENCRYPT DATA
            using (k1 = CreateAes())
            {
                k1.Key = header.AES_Key.ToArray();
                AesHelper.AesEncrypt(k1, outbuff[(Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset)..],
                    inbuff.Slice(Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset, header.data_size));
                //byte[] encData = AesHelper.AesEncrypt(k1, inbuff.ToArray(),
                //    Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset,
                //    chk_size);
                //encData.CopyTo(outbuff.Slice(Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset));
            }


            // CMAC HASHES
            using (cmac_key = CreateAes())
            {
                cmac_key.Key = header.CMAC_key.ToArray();
                //var cmac_header_hash = AesHelper.Cmac(cmac_key, outbuff.ToArray(), 0x60, 0x30);
                //var cmac_data_hash = AesHelper.Cmac(cmac_key, outbuff.ToArray(), 0x60, 0x30 + chk_size);
                Span<byte> cmac_header_hash = stackalloc byte[16];
                Span<byte> cmac_data_hash = stackalloc byte[16];
                AesHelper.Cmac(cmac_key, cmac_header_hash, outbuff.Slice(0x60, 0x30));
                AesHelper.Cmac(cmac_key, cmac_data_hash, outbuff.Slice(0x60, 0x30 + chk_size));
                cmac_header_hash.CopyTo(header.CMAC_header_hash);
                cmac_data_hash.CopyTo(header.CMAC_data_hash);
            }

            // ENCRYPT KEYS
            // AesHelper.AesEncrypt(aes_kirk1)
            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD1(Span<byte> outbuff, ReadOnlySpan<byte> inbuff, int size)
        {
            KIRK_CMD1_HEADER header = MemoryMarshal.AsRef<KIRK_CMD1_HEADER>(inbuff);
            // header_keys keys; //0-15 AES key, 16-31 CMAC key
            Aes k1;

            if (size < 0x90) return KIRK_INVALID_SIZE;
            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;
            if (header.mode != KIRK_MODE_CMD1) return KIRK_INVALID_MODE;
            // byte[] keytmp = AesHelper.AesDecrypt(aes_kirk1, inbuff.ToArray(), 0, 32);
            Span<byte> keytmp = stackalloc byte[32];
            AesHelper.AesDecrypt(aes_kirk1, keytmp, inbuff, 32);


            if (header.ecdsa_hash == 1)
            {
                KIRK_CMD1_ECDSA_HEADER eheader = MemoryMarshal.AsRef<KIRK_CMD1_ECDSA_HEADER>(inbuff);
                var curve = ECDsaHelper.SetCurve(KeyVault.ec_p, KeyVault.ec_a, KeyVault.ec_b1, KeyVault.ec_N1, KeyVault.Gx1,
                    KeyVault.Gy1);
                unsafe
                {
                    var ecdsa = ECDsaHelper.Create(curve, KeyVault.ec_Priv1);
                    if (!ecdsa.VerifyData(inbuff.Slice(0x60, 0x30), eheader.header_sig.ToArray(),
                        HashAlgorithmName.SHA1))
                    {
                        return KIRK_HEADER_HASH_INVALID;
                    }
                    ecdsa = ECDsaHelper.Create(curve, KeyVault.ec_Priv1);
                    if (!ecdsa.VerifyData(inbuff[0x60..size], eheader.data_sig.ToArray(), HashAlgorithmName.SHA1))
                    {
                        return KIRK_DATA_HASH_INVALID;
                    }
                }
            }
            else
            {
                int ret = kirk_CMD10(inbuff, size);
                if (ret != KIRK_OPERATION_SUCCESS) return ret;
            }

            k1 = CreateAes();
            k1.Key = keytmp.Slice(0, 16).ToArray();
            //var outtmp = AesHelper.AesDecrypt(k1, inbuff.ToArray(), Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset, header.data_size);
            // Buffer.BlockCopy(outtmp, 0, outbuff, 0, (int)header.data_size);

            AesHelper.AesDecrypt(k1, outbuff, inbuff.Slice(Unsafe.SizeOf<KIRK_CMD1_HEADER>() + header.data_offset), header.data_size);

            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD4(Span<byte> outbuff, ReadOnlySpan<byte> inbuff, int size)
        {
            KIRK_AES128CBC_HEADER header = MemoryMarshal.AsRef<KIRK_AES128CBC_HEADER>(inbuff);
            byte[] key;
            Aes aes;

            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;
            if (header.mode != KIRK_MODE_ENCRYPT_CBC) return KIRK_INVALID_MODE;
            if (header.data_size == 0) return KIRK_DATA_SIZE_ZERO;
            // size = size - 20;
            using (aes = CreateAes())
            {
                key = kirk_4_7_get_key(header.keyseed);

                // Set the key
                aes.Key = key;
                //var enc = AesHelper.AesEncrypt(aes, inbuff.ToArray(), Unsafe.SizeOf<KIRK_AES128CBC_HEADER>(), size);
                // Buffer.BlockCopy(enc, 0, outbuff, Marshal.SizeOf<KIRK_AES128CBC_HEADER>() + outOffset, size);
                //enc.AsSpan(0, size).CopyTo(outbuff.Slice(Unsafe.SizeOf<KIRK_AES128CBC_HEADER>(), size));
                AesHelper.AesEncrypt(aes, outbuff.Slice(Unsafe.SizeOf<KIRK_AES128CBC_HEADER>()),
                    inbuff.Slice(Unsafe.SizeOf<KIRK_AES128CBC_HEADER>(), header.data_size));
            }
            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD7(Span<byte> outbuff, ReadOnlySpan<byte> inbuff, int size)
        {
            KIRK_AES128CBC_HEADER header = MemoryMarshal.AsRef<KIRK_AES128CBC_HEADER>(inbuff);
            byte[] key;
            Aes aes;

            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;
            if (header.mode != KIRK_MODE_DECRYPT_CBC) return KIRK_INVALID_MODE;
            if (header.data_size == 0) return KIRK_DATA_SIZE_ZERO;
            using (aes = CreateAes())
            {
                key = kirk_4_7_get_key(header.keyseed);

                // Set the key
                aes.Key = key;
                //var enc = AesHelper.AesDecrypt(aes, inbuff.ToArray(), Unsafe.SizeOf<KIRK_AES128CBC_HEADER>(), header.data_size);
                //Buffer.BlockCopy(enc, 0, outbuff, outOffset, header.data_size);
                //enc.AsSpan(0, header.data_size).CopyTo(outbuff);
                AesHelper.AesDecrypt(aes, outbuff, inbuff.Slice(Unsafe.SizeOf<KIRK_AES128CBC_HEADER>()), header.data_size);
            }
            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD10(ReadOnlySpan<byte> inbuff, int insize)
        {
            KIRK_CMD1_HEADER header = MemoryMarshal.AsRef<KIRK_CMD1_HEADER>(inbuff);
            // header_keys keys; //0-15 AES key, 16-31 CMAC key
            Span<byte> cmac_header_hash = stackalloc byte[16];
            Span<byte> cmac_data_hash = stackalloc byte[16];
            using Aes cmac_key = CreateAes();
            int chk_size;

            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;
            if (!(header.mode == KIRK_MODE_CMD1 || header.mode == KIRK_MODE_CMD2 || header.mode == KIRK_MODE_CMD3)) return KIRK_INVALID_MODE;
            if (header.data_size == 0) return KIRK_DATA_SIZE_ZERO;
            if (header.mode == KIRK_MODE_CMD1)
            {
                // var decdata = AesHelper.AesDecrypt(aes_kirk1, inbuff.ToArray(), 0, 32);
                Span<byte> tmpKey = stackalloc byte[32];
                AesHelper.AesDecrypt(aes_kirk1, tmpKey, inbuff, 32);
                cmac_key.Key = tmpKey[16..].ToArray();
                //cmac_header_hash = AesHelper.Cmac(cmac_key, inbuff.ToArray(), 0x60, 0x30);
                AesHelper.Cmac(cmac_key, cmac_header_hash, inbuff.Slice(0x60, 0x30));

                // Make sure data is 16 aligned
                chk_size = header.data_size;
                if (chk_size % 16 != 0) chk_size += 16 - (chk_size % 16);
                //cmac_data_hash = AesHelper.Cmac(cmac_key, inbuff.ToArray(), 0x60, 0x30 + chk_size + header.data_offset);
                AesHelper.Cmac(cmac_key, cmac_data_hash, inbuff.Slice(0x60, 0x30 + chk_size + header.data_offset));

                if (!header.CMAC_header_hash.SequenceEqual(cmac_header_hash)) return KIRK_HEADER_HASH_INVALID;
                if (!header.CMAC_data_hash.SequenceEqual(cmac_data_hash)) return KIRK_DATA_HASH_INVALID;

                return KIRK_OPERATION_SUCCESS;
            }

            return KIRK_SIG_CHECK_INVALID; //Checks for cmd 2 & 3 not included right now
        }

        static int kirk_CMD11(Span<byte> outbuff, ReadOnlySpan<byte> inbuff, int size)
        {
            KIRK_SHA1_HEADER header = MemoryMarshal.AsRef<KIRK_SHA1_HEADER>(inbuff);
            if (!is_kirk_initialized) return KIRK_NOT_INITIALIZED;
            if (header.data_size == 0 || size == 0) return KIRK_DATA_SIZE_ZERO;

            //byte[] buff = new byte[header.data_size + 4];
            //using (var ms = new MemoryStream(buff))
            //{
            //    using var bw = new BinaryWriter(ms);
            //    bw.Write(inbuff.data_size);
            //    bw.Write(inbuff.data);
            //}
            var sha = SHA1.Create();
            var hash = sha.ComputeHash(inbuff.Slice(4, header.data_size).ToArray());
            // Buffer.BlockCopy(hash, 0, outbuff, 0, hash.Length);
            hash.CopyTo(outbuff);
            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD12(Span<byte> outbuff, int outsize)
        {
            if (outsize != 0x3C) return KIRK_INVALID_SIZE;

            var (d, q) = ECDsaHelper.GenerateKey(KeyVault.ec_p, KeyVault.ec_a, KeyVault.ec_b2, KeyVault.ec_N2, KeyVault.Gx2,
                 KeyVault.Gy2);
            d.AsSpan(0, 0x14).CopyTo(outbuff);
            q.X.AsSpan(0, 0x14).CopyTo(outbuff.Slice(0x14));
            q.Y.AsSpan(0, 0x14).CopyTo(outbuff.Slice(0x28));
            //Buffer.BlockCopy(d, 0, outbuff, 0, 0x14);
            //Buffer.BlockCopy(q.X, 0, outbuff, 0x14, 0x14);
            //Buffer.BlockCopy(q.Y, 0, outbuff, 0x28, 0x14);

            return KIRK_OPERATION_SUCCESS;
        }

        //static int kirk_CMD13()

        static int kirk_CMD14(Span<byte> outbuff, int outsize)
        {
            Span<byte> temp = stackalloc byte[0x104];

            // Some randomly selected data for a "key" to add to each randomization
            Span<byte> key = stackalloc byte[] { 0xA7, 0x2E, 0x4C, 0xB6, 0xC3, 0x34, 0xDF, 0x85, 0x70, 0x01, 0x49, 0xFC, 0xC0, 0x87, 0xC4, 0x77 };
            uint curtime;

            if (outsize <= 0) return KIRK_OPERATION_SUCCESS;
            // Buffer.BlockCopy(PRNG_DATA, 0, header.data, 0, 0x14);
            PRNG_DATA.CopyTo(temp.Slice(4));

            // This uses the standard C time function for portability.
            curtime = (uint)DateTimeOffset.Now.ToUnixTimeMilliseconds();
            temp[0x18] = (byte)(curtime & 0xFF);
            temp[0x19] = (byte)((curtime >> 8) & 0xFF);
            temp[0x1A] = (byte)((curtime >> 16) & 0xFF);
            temp[0x1B] = (byte)((curtime >> 24) & 0xFF);
            // Buffer.BlockCopy(key, 0, header.data, 0x18, 0x10);
            key.CopyTo(temp[0x1C..]);

            // This leaves the remainder of the 0x100 bytes in temp to whatever remains on the stack 
            // in an uninitialized state. This should add unpredicableness to the results as well
            // header.data_size = 0x100;
            var header = new KIRK_SHA1_HEADER
            {
                data_size = 0x100
            };
            MemoryMarshal.Write(temp, ref header);
            kirk_CMD11(PRNG_DATA, temp, 0x104);

            while (outsize > 0)
            {
                int blockrem = outsize % 0x14;
                int block = outsize / 0x14;

                if (block > 0)
                {
                    // Buffer.BlockCopy(PRNG_DATA, 0, outbuff, outoff, 0x14);
                    PRNG_DATA.CopyTo(outbuff);
                    outsize -= 0x14;
                    kirk_CMD14(outbuff[0x14..], outsize);
                }
                else if (blockrem > 0)
                {
                    // Buffer.BlockCopy(PRNG_DATA, 0, outbuff, outoff, blockrem);
                    PRNG_DATA.AsSpan(0, blockrem).CopyTo(outbuff);
                    outsize -= blockrem;
                }
            }

            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD16(Span<byte> outbuff, int outsize, ReadOnlySpan<byte> inbuff, int insize)
        {
            byte[] dec_private = new byte[0x20];
            KIRK_CMD16_BUFFER signbuf = MemoryMarshal.AsRef<KIRK_CMD16_BUFFER>(inbuff);
            //ECDSA_SIG sig = BufferToStruct<ECDSA_SIG>(outbuff);

            if (insize != 0x34) return KIRK_INVALID_SIZE;
            if (outsize != 0x28) return KIRK_INVALID_SIZE;


            decrypt_kirk16_private(dec_private, signbuf.enc_private);

            // Clear out the padding for safety
            //Array.Clear(dec_private, 0x14, 0xC);

            var curve = ECDsaHelper.SetCurve(KeyVault.ec_p, KeyVault.ec_a, KeyVault.ec_b2, KeyVault.ec_N2, KeyVault.Gx2,
                KeyVault.Gy2);
            var ecdsa = ECDsaHelper.Create(curve, dec_private.Take(0x14).ToArray());
            unsafe
            {
                var hashSig = ecdsa.SignHash(signbuf.message_hash.ToArray());
                // Buffer.BlockCopy(hashSig, 0, outbuff, outoffset, 0x28);
                hashSig.CopyTo(outbuff);
            }

            return KIRK_OPERATION_SUCCESS;
        }

        static int kirk_CMD17(ReadOnlySpan<byte> inbuff, int insize)
        {
            KIRK_CMD17_BUFFER sig = MemoryMarshal.AsRef<KIRK_CMD17_BUFFER>(inbuff);

            if (insize != 0x64) return KIRK_INVALID_SIZE;

            var curve = ECDsaHelper.SetCurve(KeyVault.ec_p, KeyVault.ec_a, KeyVault.ec_b2, KeyVault.ec_N2, KeyVault.Gx2,
                KeyVault.Gy2);

            unsafe
            {
                var ecdsa = ECDsaHelper.Create(curve, sig.public_key.x, sig.public_key.y);

                if (ecdsa.VerifyHash(sig.message_hash, sig.signature.sig))
                {
                    return KIRK_OPERATION_SUCCESS;

                }
            }
            return KIRK_SIG_CHECK_INVALID;
        }

        // SCE functions
        public static int sceUtilsBufferCopyWithRange(Span<byte> outbuff, int outsize, ReadOnlySpan<byte> inbuff, int insize, int cmd)
        {
            switch (cmd)
            {
                case KIRK_CMD_DECRYPT_PRIVATE: return kirk_CMD1(outbuff, inbuff, insize);
                case KIRK_CMD_ENCRYPT_IV_0: return kirk_CMD4(outbuff, inbuff, insize);
                case KIRK_CMD_DECRYPT_IV_0: return kirk_CMD7(outbuff, inbuff, insize);
                case KIRK_CMD_PRIV_SIGN_CHECK: return kirk_CMD10(inbuff, insize);
                case KIRK_CMD_SHA1_HASH: return kirk_CMD11(outbuff, inbuff, insize);
                case KIRK_CMD_ECDSA_GEN_KEYS: return kirk_CMD12(outbuff, outsize);
                //case KIRK_CMD_ECDSA_MULTIPLY_POINT: return kirk_CMD13(outbuff, outoffset, outsize, inbuff, insize);
                case KIRK_CMD_PRNG: return kirk_CMD14(outbuff, outsize);
                case KIRK_CMD_ECDSA_SIGN: return kirk_CMD16(outbuff, outsize, inbuff, insize);
                case KIRK_CMD_ECDSA_VERIFY: return kirk_CMD17(inbuff, insize);
            }
            return -1;
        }
    }
}
