using PspCrypto.Security.Cryptography;
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace PspCrypto
{
    public static class SceNpDrm
    {
        unsafe struct SceEbootPbp
        {
            public ulong Magic;
            public int KeyType;
            public int Type;
            private fixed byte _contentId[0x30];

            public Span<byte> ContentId
            {
                get
                {
                    fixed (byte* ptr = _contentId)
                    {
                        return new Span<byte>(ptr, 0x30);
                    }
                }
            }

            public ulong Aid;
            public ulong SecureTick;
            public long PbpSize;
            public int SwVer;
            public int DiscCount;
            private fixed long _discOffsets[6];

            public Span<long> DiscOffsets
            {
                get
                {
                    fixed (long* ptr = _discOffsets)
                    {
                        return new Span<long>(ptr, 6);
                    }
                }
            }
            private fixed byte _padding[0xC8];
            private fixed byte _pbpHdrSig[0x38];

            public Span<byte> PbpHdrSig
            {
                get
                {
                    fixed (byte* ptr = _pbpHdrSig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
            private fixed byte _npUmdImgSig[0x38];

            public Span<byte> NpUmdImgSig
            {
                get
                {
                    fixed (byte* ptr = _npUmdImgSig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
            private fixed byte _sig[0x38];

            public Span<byte> Sig
            {
                get
                {
                    fixed (byte* ptr = _sig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
        }
        unsafe struct SceEbootPbp100
        {
            public ulong Magic;
            public int KeyType;
            public int Type;
            private fixed byte _contentId[0x30];

            public Span<byte> ContentId
            {
                get
                {
                    fixed (byte* ptr = _contentId)
                    {
                        return new Span<byte>(ptr, 0x30);
                    }
                }
            }
            private fixed byte _padding[0x18];
            private fixed byte _pbpHdrSig[0x38];

            public Span<byte> PbpHdrSig
            {
                get
                {
                    fixed (byte* ptr = _pbpHdrSig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
            private fixed byte _npUmdImgSig[0x38];

            public Span<byte> NpUmdImgSig
            {
                get
                {
                    fixed (byte* ptr = _npUmdImgSig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
            private fixed byte _sig[0x38];

            public Span<byte> Sig
            {
                get
                {
                    fixed (byte* ptr = _sig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
        }

        unsafe struct SceDiscInfo
        {
            private fixed byte _id[0x30];

            public Span<byte> Id
            {
                get
                {
                    fixed (byte* ptr = _id)
                    {
                        return new Span<byte>(ptr, 0x30);
                    }
                }
            }

            public int Version;
            public int DiscCount;
            public long FileSize;
            private fixed long _diskOffsets[6];

            public Span<long> Offsets
            {
                get
                {
                    fixed (long* ptr = _diskOffsets)
                    {
                        return new Span<long>(ptr, 6);
                    }
                }
            }

            private fixed byte _pad[0x20];
            private fixed byte _discsSig[0x38];

            public Span<byte> DiscsSig
            {
                get
                {
                    fixed (byte* ptr = _discsSig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }
            private fixed byte _sig[0x38];

            public Span<byte> Sig
            {
                get
                {
                    fixed (byte* ptr = _sig)
                    {
                        return new Span<byte>(ptr, 0x38);
                    }
                }
            }

        }

        public static ulong Aid { get; set; }

        public static byte[] SceDiskInfo => Resource.__sce_discinfo;

        private const int BLK_SIZE = 0x7C0;

        private static readonly Memory<byte> _memory = new byte[0x1000];

        private static byte[] _psId;

        private static long _fuseId;

        public static void SetPSID(byte[] psid)
        {
            _psId = psid;
        }

        public static void SetFuseId(long fuseId)
        {
            _fuseId = fuseId;
        }

        public static void SetFuseId(Span<byte> fuseId)
        {
            SetFuseId(MemoryMarshal.Read<long>(fuseId));
        }

        public unsafe struct SceNpDrmKey
        {
            private fixed byte _KeyData[16];
            public Span<byte> KeyData
            {
                get
                {
                    fixed (byte* ptr = _KeyData)
                    {
                        return new Span<byte>(ptr, 16);
                    }
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct Rif
        {
            private short _version;
            private short _versionFlag;
            private int _drmType;
            private fixed byte _accountId[8];
            private fixed byte _contentId[0x30];
            private fixed byte _encKey1[0x10];
            private fixed byte _encKey2[0x10];
            private long _startTime;
            private long _endTime;
            private fixed byte _ecdsaSig[0x28];
            public short Version => _version;
            public short VersionFlag => _versionFlag;
            public int DrmType => _drmType;
            public Span<byte> AccountId
            {
                get
                {
                    fixed (byte* ptr = _accountId)
                    {
                        return new Span<byte>(ptr, 8);
                    }
                }
            }
            public Span<byte> ContentId
            {
                get
                {
                    fixed (byte* ptr = _contentId)
                    {
                        return new Span<byte>(ptr, 0x30);
                    }
                }
            }
            public Span<byte> EncKey1
            {
                get
                {
                    fixed (byte* ptr = _encKey1)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public Span<byte> EncKey2
            {
                get
                {
                    fixed (byte* ptr = _encKey2)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public long StartTime => _startTime;
            public long EndTime => _endTime;

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

        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct Act
        {
            private short _actType;
            private short _versionFlag;
            private int _version;
            private fixed byte _accountId[8];
            private fixed byte _primKeyTable[0x800];
            private fixed byte _unk1[0x40];
            private fixed byte _openPsId[0x10];
            private fixed byte _unk2[0x10];
            private fixed byte _unk4[0x10];
            private fixed byte _secondTable[0x650];
            private fixed byte _rsaSig[0x100];
            private fixed byte _unkSig[0x40];
            private fixed byte _ecdsaSig[0x28];
            public short ActType => _actType;
            public short VersionFlag => _versionFlag;
            public int Version => _version;
            public Span<byte> AccountId
            {
                get
                {
                    fixed (byte* ptr = _accountId)
                    {
                        return new Span<byte>(ptr, 8);
                    }
                }
            }
            public Span<byte> PrimKeyTable
            {
                get
                {
                    fixed (byte* ptr = _primKeyTable)
                    {
                        return new Span<byte>(ptr, 0x800);
                    }
                }
            }
            public Span<byte> Unk1
            {
                get
                {
                    fixed (byte* ptr = _unk1)
                    {
                        return new Span<byte>(ptr, 0x40);
                    }
                }
            }
            public Span<byte> OpenPsId
            {
                get
                {
                    fixed (byte* ptr = _openPsId)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public Span<byte> Unk2
            {
                get
                {
                    fixed (byte* ptr = _unk2)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public Span<byte> Unk4
            {
                get
                {
                    fixed (byte* ptr = _unk4)
                    {
                        return new Span<byte>(ptr, 0x10);
                    }
                }
            }
            public Span<byte> SecondTable
            {
                get
                {
                    fixed (byte* ptr = _secondTable)
                    {
                        return new Span<byte>(ptr, 0x650);
                    }
                }
            }
            public Span<byte> RsaSig
            {
                get
                {
                    fixed (byte* ptr = _rsaSig)
                    {
                        return new Span<byte>(ptr, 0x100);
                    }
                }
            }
            public Span<byte> UnkSig
            {
                get
                {
                    fixed (byte* ptr = _unkSig)
                    {
                        return new Span<byte>(ptr, 0x40);
                    }
                }
            }
            public Span<byte> EcdsaSig
            {
                get
                {
                    fixed (byte* ptr = _ecdsaSig)
                    {
                        return new Span<byte>(ptr, 0x28);
                    }
                }
            }

        }
        public static int sceNpDrmTransformVersionKey(Span<byte> versionKey, int srcKeyType, int dstKeyType)
        {
            // reverse the previous "GenVersionKey" step ..
            int ret = ReverseGenVersionKey(versionKey, srcKeyType);
            if (ret >= 0)
                ret = GenVersionKey(versionKey, dstKeyType); // generate a new version key of this type
            return ret;
        }

        public static int sceNpDrmGetIDps(Span<byte> idps)
        {
            if (_psId != null && idps != null)
            {
                _psId.CopyTo(idps);
                return 0;
            }
            return -0x7faaf6ff;
        }

        public static int GetActKey(Span<byte> key, ReadOnlySpan<byte> keyTable, int count)
        {
            Span<byte> idps = stackalloc byte[16];
            Span<byte> decKey = stackalloc byte[16];
            int ret = sceNpDrmGetIDps(idps);
            if (ret > -1)
            {
                ret = AesHelper.AesEncrypt(KeyVault.drmActdatKey, decKey, idps);
                if (ret == 16)
                {
                    for (int i = 0; i < count; i++)
                    {
                        ret = AesHelper.AesDecrypt(keyTable[(i * 0x10)..], key[(i * 0x10)..], decKey);
                        if (ret != 16)
                        {
                            ret = unchecked((int)0x80550902);
                            break;
                        }
                    }
                    ret = 0;
                }
            }
            idps.Clear();
            decKey.Clear();
            return ret;
        }

        private static int GetFreeVersionKey(Span<byte> versionKey, ReadOnlySpan<byte> rifBuf)
        {
            return -1;
        }

        private static int GetLicenseVersionKey(Span<byte> versionKey, ReadOnlySpan<byte> actBuf, ReadOnlySpan<byte> rifBuf)
        {
            Span<byte> buf1 = stackalloc byte[16];
            Span<byte> buf2 = stackalloc byte[16];
            int ret = sceNpDrmVerifyAct(actBuf);
            if (ret > -1)
            {
                ret = sceNpDrmVerifyRif(rifBuf);
                if (ret > -1)
                {
                    var act = MemoryMarshal.AsRef<Act>(actBuf);
                    var rif = MemoryMarshal.AsRef<Rif>(rifBuf);
                    if (!act.AccountId.SequenceEqual(rif.AccountId))
                    {
                        return -0x7faaf6fb;
                    }
                    if (((act.ActType & 0xff) << 8 | act.ActType >> 8) < ((rif.Version & 0xff) << 8 | rif.Version >> 9))
                    {
                        return -0x7faaf6ef;
                    }
                    AesHelper.AesDecrypt(rif.EncKey1, buf1, KeyVault.drmRifKey);
                    int keyIdx = MemoryMarshal.Read<int>(buf1[12..]);
                    keyIdx >>= 0x18;
                    if (keyIdx >= 0x80)
                    {
                        return -0x7faaf6fe;
                    }
                    ret = GetActKey(buf2, act.PrimKeyTable[(keyIdx * 0x10)..], 1);
                    if (ret > -1)
                    {
                        ret = AesHelper.AesDecrypt(rif.EncKey2, versionKey, buf2);
                        if (ret != 16)
                        {
                            ret = -0x7faaf6fe;
                        }
                    }
                }
            }
            buf1.Clear();
            buf2.Clear();
            return ret;
        }

        private static int CheckRifExpress(ReadOnlySpan<byte> rifBuf)
        {
            // TODO
            return 0;
        }

        
        private static int ReverseGenVersionKey(Span<byte> versionkey, int type)
        {
            type &= 0xffffff;
            int ret = 0;
            if (type != 0)
            {
                ret = unchecked((int)0x80550901);
                if (type < 4)
                {
                    ret = AesHelper.AesDecrypt(versionkey, versionkey, KeyVault.drmVersionKeyKey.AsSpan().Slice(type * 0x10, 0x10));
                    ret = ret == 16 ? 0 : unchecked((int)0x80550902);
                }
            }
            return ret;
        }
        private static int GenVersionKey(Span<byte> versionKey, int type)
        {
            type &= 0xffffff;
            int ret = 0;
            if (type != 0)
            {
                ret = unchecked((int)0x80550901);
                if (type < 4)
                {
                    ret = AesHelper.AesEncrypt(versionKey, versionKey, KeyVault.drmVersionKeyKey.AsSpan().Slice(type * 0x10, 0x10));
                    ret = ret == 16 ? 0 : unchecked((int)0x80550902);
                }
            }
            return ret;
        }

        public static int sceNpDrmGetVersionKey(Span<byte> versionKey, ReadOnlySpan<byte> actBuf, ReadOnlySpan<byte> rifBuf, int type)
        {
            int ret = -0x7faaf6ff;
            if (versionKey != null && rifBuf != null)
            {
                var rif = MemoryMarshal.AsRef<Rif>(rifBuf);
                if (rif.DrmType == 0x300000)
                {
                    ret = GetFreeVersionKey(versionKey, rifBuf);
                }
                else
                {
                    if (actBuf == null)
                    {
                        return ret;
                    }
                    ret = GetLicenseVersionKey(versionKey, actBuf, rifBuf);
                }
                if (ret > -1)
                {
                    ret = CheckRifExpress(rifBuf);
                    if (ret > -1)
                    {
                        ret = GenVersionKey(versionKey, type);
                    }
                }
            }
            return ret;
        }

        public static int sceNpDrmGetFixedKey(Span<byte> fixedKey, ReadOnlySpan<byte> contentId, int type)
        {
            Span<byte> buf = stackalloc byte[0x30];
            Span<byte> mkey = stackalloc byte[48];
            int ret = -0x7faaf6ff;
            if ((type & 0x1000000) != 0)
            {
                contentId[..0x30].CopyTo(buf);
                ret = AMCTRL.sceDrmBBMacInit(mkey, 1);
                if (ret == 0)
                {
                    ret = AMCTRL.sceDrmBBMacUpdate(mkey, buf, 0x30);
                    if (ret == 0)
                    {
                        ret = AMCTRL.sceDrmBBMacFinal(mkey, fixedKey, KeyVault.DrmFixedKey);
                        if (ret == 0)
                        {
                            ret = GenVersionKey(fixedKey, (int)(type & 0xfeffffff));
                        }
                        else
                        {
                            ret = -0x7faaf6fe;
                        }
                    }
                }
            }
            return ret;
        }

        public static int sceNpDrmGetContentKey(Span<byte> contentKey, ReadOnlySpan<byte> actBuf, ReadOnlySpan<byte> rifBuf) => sceNpDrmGetVersionKey(contentKey, actBuf, rifBuf, 0);

        public static int sceNpDrmVerifyAct(ReadOnlySpan<byte> actBuf)
        {
            int ret = -0x7faaf6ff;
            if (actBuf != null)
            {
                ret = -0x7faaf6fa;
                var act = MemoryMarshal.AsRef<Act>(actBuf);
                if (((act.VersionFlag & 0xFF) << 8 | act.VersionFlag >> 8) < 2)
                {
                    if (((act.ActType & 0xFF) << 8 | act.ActType >> 8) < 2)
                    {
                        ret = VerifySig(act.EcdsaSig, KeyVault.drmActRifSig, actBuf, 0x1010);
                    }
                }
            }
            return ret;
        }

        private static int VerifySig(ReadOnlySpan<byte> sig, ReadOnlySpan<byte> pubKey, ReadOnlySpan<byte> data, int dataSize)
        {
            int ret;
            Span<byte> digest = stackalloc byte[32];
            if (dataSize < 0x801)
            {
                ret = SceDdrdb.sceDdrdbHash(data, dataSize, digest);
            }
            else
            {
                ret = (SHA1.HashData(data[..dataSize], digest) > 0 ? 0 : -1);
            }
            if (ret > -1)
            {
                ret = SceDdrdb.sceDdrdbSigvry(pubKey, digest, sig);
            }
            return ret;
        }

        private static int FillFuseId(Span<uint> fuseId)
        {
            if (_fuseId != 0)
            {
                uint heigh = (uint)((_fuseId >> 32) & 0xffffffff);
                uint low = (uint)(_fuseId & 0xffffffff);
                fuseId[0] = BinaryPrimitives.ReverseEndianness(heigh);
                fuseId[1] = BinaryPrimitives.ReverseEndianness(low);
            }
            return -1;
        }

        private static int VerifyRif(ReadOnlySpan<byte> rifBuf)
        {
            int ret = -0x7faaf6ff;
            if (rifBuf != null)
            {
                ret = -0x7faaf6fa;
                var rif = MemoryMarshal.AsRef<Rif>(rifBuf);
                if (((rif.VersionFlag & 0xff) << 8 | rif.VersionFlag >> 8) < 3)
                {
                    if (((rif.Version & 0xff) << 8 | rif.Version >> 8) < 2)
                    {
                        int ret2;
                        if ((rif.DrmType & 0x10000) == 0)
                        {
                            ret = VerifySig(rif.EcdsaSig, KeyVault.drmActRifSig, rifBuf, 0x70);
                            ret2 = -0x7faaf6fc;
                        }
                        else
                        {
                            Span<byte> buffer = stackalloc byte[0x90];
                            rifBuf[..0x70].CopyTo(buffer);
                            if (_psId == null)
                            {
                                return ret;
                            }
                            ret = sceNpDrmGetIDps(buffer[0x70..]);
                            if (ret < 0)
                            {
                                return ret;
                            }
                            Span<uint> fuseId = MemoryMarshal.Cast<byte, uint>(buffer[0x80..]);
                            ret = FillFuseId(fuseId);
                            if (ret < 0)
                            {
                                return ret;
                            }
                            ret = VerifySig(rif.EcdsaSig, KeyVault.drmActRifSig, buffer, 0x90);
                            ret2 = -0x7faaf6e8;
                        }
                        if (ret != 0)
                        {
                            ret = ret2;
                        }
                    }
                }
            }
            return ret;
        }

        public static int sceNpDrmVerifyRif(ReadOnlySpan<byte> rifBuf)
        {
            int ret = VerifyRif(rifBuf);
            if (ret > -1)
            {
                var rif = MemoryMarshal.AsRef<Rif>(rifBuf);
                if (rif.DrmType == 0x3000000)
                {
                    ret = -1; // TODO
                }
            }
            return ret;
        }

        public static int KsceNpDrmEbootSigGenMultiDisc(string fileName, ReadOnlySpan<byte> sceDiscInfo,
            Span<byte> ebootSig, int swVer)
        {
            return SceNpDrmEbootSigGenMultiDisc(fileName, sceDiscInfo, ebootSig, swVer, _memory.Span, BLK_SIZE);
        }
        public static int KsceNpDrmEbootSigGenPs1(Stream file, Span<byte> ebootSig, int swVer)
        {
            return SceNpDrmEbootSigGen(file, 3, ebootSig, swVer, _memory.Span, BLK_SIZE);
        }

        public static int KsceNpDrmEbootSigGenPsp(Stream file, Span<byte> ebootSig, int swVer)
        {
            return SceNpDrmEbootSigGen(file, 2, ebootSig, swVer, _memory.Span, BLK_SIZE);
        }

        public static int KsceNpDrmPspEbootSigGen(Stream file, Span<byte> ebootSig)
        {
            return SceNpDrmPspEbootSigGen(file, ebootSig, _memory.Span, BLK_SIZE);
        }

        public static int KsceNpDrmEbootSigGenPs1(string fileName, Span<byte> ebootSig, int swVer)
        {
            return SceNpDrmEbootSigGen(fileName, 3, ebootSig, swVer, _memory.Span, BLK_SIZE);
        }

        public static int KsceNpDrmEbootSigGenPsp(string fileName, Span<byte> ebootSig, int swVer)
        {
            return SceNpDrmEbootSigGen(fileName, 2, ebootSig, swVer, _memory.Span, BLK_SIZE);
        }

        public static int KsceNpDrmPspEbootSigGen(string fileName, Span<byte> ebootSig)
        {
            return SceNpDrmPspEbootSigGen(fileName, ebootSig, _memory.Span, BLK_SIZE);
        }

        private static int SceNpDrmEbootSigGenMultiDisc(string fileName, ReadOnlySpan<byte> sceDiskInfo,
            Span<byte> ebootSig, int swVer, Span<byte> buffer, int blockSize)
        {
            Span<byte> pbpHdrDigest = stackalloc byte[32];
            Span<byte> discsDigest = stackalloc byte[32];
            Span<byte> ebootSigtmp = stackalloc byte[512];
            ref var sceEbootPbp = ref MemoryMarshal.AsRef<SceEbootPbp>(ebootSigtmp);
            if ((blockSize & 0x3F) != 0)
            {
                blockSize &= unchecked((int)0xFFFFFFC0);
            }

            if (sceDiskInfo == null || string.IsNullOrWhiteSpace(fileName) || buffer == null || ebootSig == null ||
                blockSize < 0x400)
            {
                return unchecked((int)0x80870001);
            }

            Span<byte> secureTick = stackalloc byte[8] { 0xD4, 0x7A, 0x2C, 0x13, 0x64, 0x59, 0xE2, 0x00 };
            //RandomNumberGenerator.Fill(secureTick);

            ebootSig.Fill(0);
            sceEbootPbp.SwVer = swVer;
            sceEbootPbp.Aid = Aid;
            sceEbootPbp.SecureTick = MemoryMarshal.AsRef<ulong>(secureTick);

            var sha224 = SHA224.Create();
            var hash = sha224.ComputeHash(sceDiskInfo[..200].ToArray());
            var ret = SceSblGcAuthMgrDrmBBForDriver_4B506BE7(hash, sceDiskInfo[200..], 1000);
            if (ret != 0)
            {
                return ret;
            }

            var discInfo = MemoryMarshal.AsRef<SceDiscInfo>(sceDiskInfo);
            ret = unchecked((int)0x80870005);
            if (discInfo.DiscCount > 6)
            {
                return ret;
            }
            var fi = new FileInfo(fileName);
            if (!fi.Exists)
            {
                return -1;
            }

            if (fi.Length != discInfo.FileSize)
            {
                return ret;
            }

            ret = unchecked((int)0x80870001);
            if (discInfo.DiscCount < 7)
            {
                if (fi.Length < discInfo.Offsets[0])
                {
                    return ret;
                }
                if (discInfo.DiscCount > 1 && fi.Length < discInfo.Offsets[1])
                {
                    return ret;
                }
                if (discInfo.DiscCount > 2 && fi.Length < discInfo.Offsets[2])
                {
                    return ret;
                }
                if (discInfo.DiscCount > 3 && fi.Length < discInfo.Offsets[3])
                {
                    return ret;
                }
                if (discInfo.DiscCount > 4 && fi.Length < discInfo.Offsets[4])
                {
                    return ret;
                }
                if (discInfo.DiscCount > 5 && fi.Length < discInfo.Offsets[5])
                {
                    return ret;
                }

                using var stream = fi.OpenRead();
                ret = SceMultiDiscDigest(stream, fi.Length, discInfo.DiscCount, discInfo.Offsets, pbpHdrDigest, discsDigest, buffer, blockSize);
                if (discInfo.DiscCount >= 0)
                {
                    stream.Close();
                    ret = SceSblGcAuthMgrDrmBBForDriver_4B506BE7(discsDigest, discInfo.DiscsSig, 1000);
                    if (ret != 0)
                    {
                        return ret;
                    }

                    sceEbootPbp.PbpSize = fi.Length;
                    sceEbootPbp.Magic = 0x47495349544C554D;
                    sceEbootPbp.KeyType = 1;
                    sceEbootPbp.Type = 4;
                    discInfo.Id.CopyTo(sceEbootPbp.ContentId);
                    sceEbootPbp.DiscCount = discInfo.DiscCount;
                    discInfo.Offsets.CopyTo(sceEbootPbp.DiscOffsets);
                    ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(pbpHdrDigest, sceEbootPbp.PbpHdrSig, 1);
                    if (ret != 0)
                    {
                        return ret;
                    }
                    discInfo.DiscsSig.CopyTo(sceEbootPbp.NpUmdImgSig);
                    var ebootsigDigst = sha224.ComputeHash(ebootSigtmp[..0x1C8].ToArray());
                    ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(ebootsigDigst, sceEbootPbp.Sig, 1);
                    if (ret != 0)
                    {
                        return ret;
                    }
                    ebootSigtmp.CopyTo(ebootSig);
                    ret = 0;
                }
            }
            return ret;
        }

        private static int SceMultiDiscDigest(Stream stream, long fileSize, int diskCount,
            ReadOnlySpan<long> diskOffsets, Span<byte> pbpHdrDigest, Span<byte> dataDigest, Span<byte> buffer,
            int blockSize)
        {
            var sha224 = SHA224.Create();
            var ret = unchecked((int)0x80870005);
            stream.Seek(0, SeekOrigin.Begin);
            buffer = buffer[..blockSize];
            var readSize = stream.Read(buffer);
            if (readSize < 0x28)
            {
                return ret;
            }
            var pbpHeader = MemoryMarshal.AsRef<PbpHeader>(buffer);
            if (pbpHeader.Sig != 0x50425000)
            {
                return ret;
            }

            var tmp = readSize;
            if (pbpHeader.DataPsarOff < readSize)
            {
                tmp = pbpHeader.DataPsarOff;
            }

            var paramReadSize = pbpHeader.Icon0Off;
            if (paramReadSize > 0x400)
            {
                paramReadSize = 0x400;
            }

            if (tmp < paramReadSize)
            {
                return ret;
            }

            sha224.TransformFinalBlock(buffer.ToArray(), 0, paramReadSize);
            sha224.Hash.CopyTo(pbpHdrDigest);

            sha224.Initialize();
            sha224.TransformBlock(buffer.ToArray(), 0, paramReadSize, null, 0);

            long end = pbpHeader.DataPsarOff + 0xC0000;
            if (diskOffsets[0] >= pbpHeader.DataPsarOff && diskOffsets[0] < end)
            {
                end = diskOffsets[0];
            }

            long start = pbpHeader.DataPsarOff;
            stream.Seek(start, SeekOrigin.Begin);
            for (; start < end; start += readSize)
            {
                var toRead = (int)(end - start);
                if (toRead > blockSize)
                {
                    toRead = blockSize;
                }

                readSize = stream.Read(buffer[..toRead]);
                if (readSize == 0)
                {
                    return -1;
                }
                sha224.TransformBlock(buffer.ToArray(), 0, readSize, null, 0);
            }

            if (diskCount != 0)
            {
                var start1 = diskOffsets[0];
                end = diskOffsets[0] + 0xC0000;
                if (end >= 0)
                {
                    var discNo = 0;
                    var idx = 5;
                    while (true)
                    {
                        if (fileSize < end)
                        {
                            end = fileSize;
                        }

                        if (++discNo < diskCount)
                        {
                            start = diskOffsets[idx - 4];
                            if (start >= start1 && start < end)
                            {
                                end = start;
                            }
                        }

                        if (start1 < end)
                        {
                            stream.Seek(start1, SeekOrigin.Begin);
                            while (true)
                            {
                                var toRead = (int)(end - start1);
                                if (toRead > blockSize)
                                {
                                    toRead = blockSize;
                                }

                                readSize = stream.Read(buffer[..toRead]);
                                if (readSize == 0)
                                {
                                    return -1;
                                }
                                sha224.TransformBlock(buffer.ToArray(), 0, readSize, null, 0);
                                start1 += toRead;
                                if (start1 >= end)
                                {
                                    break;
                                }
                            }
                        }

                        if (discNo == diskCount)
                        {
                            break;
                        }
                        start1 = diskOffsets[idx - 4];
                        idx++;
                        end = start1 + 0xC0000;

                    }
                }
            }
            sha224.TransformFinalBlock(buffer.ToArray(), 0, 0);
            sha224.Hash.CopyTo(dataDigest);

            stream.Seek(pbpHeader.DataPsarOff, SeekOrigin.Begin);
            readSize = stream.Read(buffer[..0x100]);
            if (readSize == 0x100)
            {
                ret = 0;
            }
            else
            {
                ret = unchecked((int)0x80870005);
            }

            return ret;
        }

        private static int SceNpDrmEbootSigGen(string fileName, int type, Span<byte> ebootSig, int swVer, Span<byte> buffer, int blockSize)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return -0x7f78ffff;
            var fi = new FileInfo(fileName);
            if (!fi.Exists)
            {
                return -1;
            }
            using (FileStream fstream = fi.OpenRead())
            {
                return SceNpDrmEbootSigGen(fstream, type, ebootSig, swVer, buffer, blockSize);
            }

        }
        private static int SceNpDrmEbootSigGen(Stream ebootStream, int type, Span<byte> ebootSig, int swVer, Span<byte> buffer, int blockSize)
        {
            Span<byte> pbpHdrDigest = stackalloc byte[32];
            Span<byte> npUmdImgDigest = stackalloc byte[32];
            Span<byte> ebootSigtmp = stackalloc byte[512];
            ref var sceEbootPbp = ref MemoryMarshal.AsRef<SceEbootPbp>(ebootSigtmp);
            if ((blockSize & 0x3F) != 0)
            {
                blockSize &= unchecked((int)0xFFFFFFC0);
            }

            if (ebootStream == null || buffer == null || ebootSig == null || blockSize < 0x400 || type > 3)
            {
                return -0x7f78ffff;
            }

            Span<byte> secureTick = BitConverter.GetBytes(SceRtc.ksceRtcGetCurrentSecureTick());

            ebootSig.Fill(0);
            sceEbootPbp.SwVer = swVer;
            sceEbootPbp.Aid = Aid;
            sceEbootPbp.SecureTick = MemoryMarshal.AsRef<ulong>(secureTick);

            long flen = ebootStream.Length;
            int ret = SceEbootPbpDigest(ebootStream, flen, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
            if (ret < 0)
            {
                return ret;
            }

            sceEbootPbp.KeyType = 1;
            sceEbootPbp.PbpSize = flen;
            sceEbootPbp.Type = type;
            var psarSig = Encoding.ASCII.GetString(buffer[..8]);
            if (type == 3)
            {
                if (psarSig != "PSISOIMG" && psarSig != "PSTITLEI")
                {
                    return -0x7f78fffb;
                }
                sceEbootPbp.Magic = 0x474953315350504E;
            }
            else
            {
                if (psarSig != "NPUMDIMG")
                {
                    return -0x7f78fffb;
                }

                sceEbootPbp.Magic = 0x474953444D55504E;
                buffer.Slice(0x10, 0x30).CopyTo(sceEbootPbp.ContentId);
            }

            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(pbpHdrDigest, sceEbootPbp.PbpHdrSig, 1);
            if (ret < 0)
            {
                return ret;
            }

            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(npUmdImgDigest, sceEbootPbp.NpUmdImgSig, 1);
            if (ret < 0)
            {
                return ret;
            }

            var sha224 = SHA224.Create();
            var ebootsigDigst = sha224.ComputeHash(ebootSigtmp[..0x1C8].ToArray());
            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(ebootsigDigst, sceEbootPbp.Sig, 1);
            if (ret < 0)
            {
                return ret;
            }
            ebootSigtmp.CopyTo(ebootSig);
            ret = 0;
            return ret;
        }

        private static int SceEbootPbpDigest(Stream stream, long fileSize, Span<byte> pbpHdrDigest,
            Span<byte> npUmdImgDigest, Span<byte> buffer, int blockSize)
        {
            var sha224 = SHA224.Create();
            var ret = unchecked((int)0x80870005);
            stream.Seek(0, SeekOrigin.Begin);
            buffer = buffer[..blockSize];
            var readSize = stream.Read(buffer);
            if (readSize < 0x28)
            {
                return ret;
            }

            var pbpHeader = MemoryMarshal.AsRef<PbpHeader>(buffer);
            if (fileSize < pbpHeader.DataPsarOff + 0xFF || pbpHeader.Sig != 0x50425000)
            {
                return ret;
            }

            var tmp = readSize;
            if (pbpHeader.DataPsarOff < readSize)
            {
                tmp = pbpHeader.DataPsarOff;
            }

            var paramReadSize = pbpHeader.Icon0Off;
            if (paramReadSize > 0x400)
            {
                paramReadSize = 0x400;
            }

            if (tmp < paramReadSize)
            {
                return ret;
            }

            sha224.TransformFinalBlock(buffer.ToArray(), 0, paramReadSize);
            sha224.Hash.CopyTo(pbpHdrDigest);

            sha224.Initialize();

            var alignedFileSize = (pbpHeader.DataPsarOff + 0x1C0000 + 64 - 1) & ~(64 - 1);
            if (alignedFileSize < fileSize)
            {
                fileSize = alignedFileSize;
            }

            var fixsize2 = fileSize;
            if (pbpHeader.DataPsarOff + 0x1C0000 < fileSize)
            {
                fixsize2 = pbpHeader.DataPsarOff + 0x1C0000;
            }

            if (pbpHeader.DataPsarOff < fixsize2)
            {
                var offset = pbpHeader.DataPsarOff;
                stream.Seek(offset, SeekOrigin.Begin);
                while (true)
                {
                    readSize = stream.Read(buffer);
                    var bsize = readSize;
                    if (readSize <= 0)
                    {
                        ret = unchecked((int)0x80870002);
                        return ret;
                    }

                    if (fixsize2 < offset + readSize)
                    {
                        bsize = (int)(fixsize2 - offset);
                    }
                    sha224.TransformBlock(buffer.ToArray(), 0, bsize, null, 0);

                    offset += readSize;
                    if (offset >= fixsize2)
                    {
                        break;
                    }
                }
                sha224.TransformFinalBlock(buffer.ToArray(), 0, 0);
                sha224.Hash.CopyTo(npUmdImgDigest);
                stream.Seek(pbpHeader.DataPsarOff, SeekOrigin.Begin);
                readSize = stream.Read(buffer[..0x100]);
                if (readSize == 0x100)
                {
                    ret = 0;
                }
                else
                {
                    ret = unchecked((int)0x80870005);
                }
            }


            return ret;
        }

        private static int SceNpDrmPspEbootSigGen(string fileName, Span<byte> ebootSig, Span<byte> buffer, int blockSize)
        {
            if (string.IsNullOrWhiteSpace(fileName))
                return -0x7f78ffff;

            var fi = new FileInfo(fileName);
            if (!fi.Exists)
            {
                return -1;
            }
            using (FileStream fs = fi.OpenRead())
            {
                return SceNpDrmPspEbootSigGen(fs, ebootSig, buffer, blockSize);
            }
        }

        private static int SceNpDrmPspEbootSigGen(Stream ebootStream, Span<byte> ebootSig, Span<byte> buffer, int blockSize)
        {
            Span<byte> pbpHdrDigest = stackalloc byte[32];
            Span<byte> npUmdImgDigest = stackalloc byte[32];
            Span<byte> ebootSigtmp = stackalloc byte[0x100];
            ref var sceEbootPbp = ref MemoryMarshal.AsRef<SceEbootPbp100>(ebootSigtmp);
            if ((blockSize & 0x3F) != 0)
            {
                blockSize &= unchecked((int)0xFFFFFFC0);
            }

            if (ebootStream == null || buffer == null || ebootSig == null || blockSize < 0x400)
            {
                return -0x7f78ffff;
            }

            long fileSize = ebootStream.Length;
            int ret = SceEbootPbpDigest(ebootStream, fileSize, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
            if (ret < 0)
            {
                return ret;
            }

            if (Encoding.ASCII.GetString(buffer[..8]) != "NPUMDIMG")
            {
                return -0x7f78fffb;
            }
            buffer[..0x40].CopyTo(ebootSigtmp);
            sceEbootPbp.Type = 0;
            sceEbootPbp.Magic = 0x474953444D55504E;

            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(pbpHdrDigest, sceEbootPbp.PbpHdrSig, 1);
            if (ret < 0)
            {
                return ret;
            }

            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(npUmdImgDigest, sceEbootPbp.NpUmdImgSig, 1);
            if (ret < 0)
            {
                return ret;
            }

            var sha224 = SHA224.Create();
            var ebootsigDigst = sha224.ComputeHash(ebootSigtmp[..0xC8].ToArray());
            ret = SceSblGcAuthMgrDrmBBForDriver_050DC6DF(ebootsigDigst, sceEbootPbp.Sig, 1);
            if (ret < 0)
            {
                return ret;
            }
            ebootSigtmp.CopyTo(ebootSig);
            ret = 0;

            return ret;
        }

        private static int SceSblGcAuthMgrDrmBBForDriver_050DC6DF(ReadOnlySpan<byte> digest, Span<byte> sig, int type)
        {
            var curve = ECDsaHelper.SetCurve(KeyVault.Eboot_p, KeyVault.Eboot_a, KeyVault.Eboot_b, KeyVault.Eboot_N, KeyVault.Eboot_Gx,
                KeyVault.Eboot_Gy);
            using var ecdsa = ECDsaHelper.Create(curve,
                KeyVault.Eboot_priv[type],
                KeyVault.Eboot_pubx[type],
                KeyVault.Eboot_puby[type], true);
            var signature = ecdsa.SignHash(digest.ToArray());
            signature.CopyTo(sig);
            return 0;
        }

        private static int SceSblGcAuthMgrDrmBBForDriver_4B506BE7(ReadOnlySpan<byte> digest, ReadOnlySpan<byte> sig, int keyType)
        {
            byte[] pubx;
            byte[] puby;
            switch (keyType)
            {
                case 1:
                    pubx = KeyVault.VitaKirk18PubKey1x;
                    puby = KeyVault.VitaKirk18PubKey1y;
                    break;
                case 0:
                    pubx = KeyVault.VitaKirk18PubKey0x;
                    puby = KeyVault.VitaKirk18PubKey0y;
                    break;
                case 1000:
                    pubx = KeyVault.VitaKirk18PubKey1000x;
                    puby = KeyVault.VitaKirk18PubKey1000y;
                    break;
                default:
                    return unchecked((int)0x808a040a);
            }
            var curve = ECDsaHelper.SetCurve(KeyVault.Eboot_p, KeyVault.Eboot_a, KeyVault.Eboot_b, KeyVault.Eboot_N, KeyVault.Eboot_Gx,
                KeyVault.Eboot_Gy);
            using var ecdsa = ECDsaHelper.Create(curve, pubx, puby);
            var verify = ecdsa.VerifyHash(digest.ToArray(), sig.ToArray());

            return verify ? 0 : -1;
        }
    }
}
