private int decryptData(FileStream ii, FileStream o, NPD npd, EDATData data, byte[] rifkey)
        {
            int numBlocks           = (int)((data.getFileLen() + data.getBlockSize() - 1) / data.getBlockSize());
            int metadataSectionSize = ((data.getFlags() & FLAG_COMPRESSED) != 0 || (data.getFlags() & FLAG_0x20) != 0) ? 0x20 : 0x10;
            int baseOffset          = 0x100; //+ offset (unknown)

            for (int i = 0; i < numBlocks; i++)
            {
                ii.Seek(baseOffset + i * metadataSectionSize, SeekOrigin.Begin);
                byte[] expectedHash = new byte[0x10];
                long   offset;
                int    len;
                int    compressionEndBlock = 0;
                if ((data.getFlags() & FLAG_COMPRESSED) != 0)
                {
                    byte[] metadata = new byte[0x20];
                    ii.Read(metadata, 0, metadata.Length);
                    byte[] result = decryptMetadataSection(metadata);
                    offset = (int)(ConversionUtils.be64(result, 0)); // + offset (unknown)
                    len    = (int)(ConversionUtils.be32(result, 8));
                    compressionEndBlock = (int)(ConversionUtils.be32(result, 0xC));
                    ConversionUtils.arraycopy(metadata, 0, expectedHash, 0, 0x10);
                }
                else if ((data.getFlags() & FLAG_0x20) != 0)
                {
                    //NOT TESTED: CASE WHERE METADATASECTION IS 0x20 BYTES LONG
                    byte[] metadata = new byte[0x20];
                    ii.Read(metadata, 0, metadata.Length);
                    for (int j = 0; j < 0x10; j++)
                    {
                        expectedHash[j] = (byte)(metadata[j] ^ metadata[j + 0x10]);
                    }
                    offset = baseOffset + i * data.getBlockSize() + numBlocks * metadataSectionSize;
                    len    = (int)(data.getBlockSize());
                    if (i == numBlocks - 1)
                    {
                        len = (int)(data.getFileLen() % (new BigInteger(data.getBlockSize())));
                    }
                }
                else
                {
                    ii.Read(expectedHash, 0, expectedHash.Length);
                    offset = baseOffset + i * data.getBlockSize() + numBlocks * metadataSectionSize;
                    len    = (int)(data.getBlockSize());
                    if (i == numBlocks - 1)
                    {
                        len = (int)(data.getFileLen() % (new BigInteger(data.getBlockSize())));
                    }
                }
                int realLen = len;
                len = (int)((uint)(len + 0xF) & 0xFFFFFFF0);
                Debug.Print("Offset: %016X, len: %08X, realLen: %08X, endCompress: %d\r\n", offset, len, realLen, compressionEndBlock);
                ii.Seek(offset, SeekOrigin.Begin);
                byte[] encryptedData = new byte[len];
                byte[] decryptedData = new byte[len];
                ii.Read(encryptedData, 0, encryptedData.Length);
                byte[] key      = new byte[0x10];
                byte[] hash     = new byte[0x10];
                byte[] blockKey = calculateBlockKey(i, npd);

                ToolsImpl.aesecbEncrypt(rifkey, blockKey, 0, key, 0, blockKey.Length);
                if ((data.getFlags() & FLAG_0x10) != 0)
                {
                    ToolsImpl.aesecbEncrypt(rifkey, key, 0, hash, 0, key.Length);
                }
                else
                {
                    ConversionUtils.arraycopy(key, 0, hash, 0, key.Length);
                }
                int cryptoFlag = ((data.getFlags() & FLAG_0x02) == 0) ? 0x2 : 0x1;
                int hashFlag;
                if ((data.getFlags() & FLAG_0x10) == 0)
                {
                    hashFlag = 0x02;
                }
                else if ((data.getFlags() & FLAG_0x20) == 0)
                {
                    hashFlag = 0x04;
                }
                else
                {
                    hashFlag = 0x01;
                }
                if ((data.getFlags() & FLAG_KEYENCRYPTED) != 0)
                {
                    cryptoFlag |= 0x10000000;
                    hashFlag   |= 0x10000000;
                }
                if ((data.getFlags() & FLAG_DEBUG) != 0)
                {
                    cryptoFlag |= 0x01000000;
                    hashFlag   |= 0x01000000;
                }
                AppLoader a  = new AppLoader();
                byte[]    iv = (npd.getVersion() <= 1) ? (new byte[0x10]) : npd.getDigest();

                bool rresult = a.doAll(hashFlag, cryptoFlag, encryptedData, 0, decryptedData, 0, encryptedData.Length, key, npd.getDigest(), hash, expectedHash, 0);
                if (!rresult)
                {
                    Debug.WriteLine("Error decrypting block " + i);
                    // KDSBest find out why block 30 errors
                    //return STATUS_ERROR_DECRYPTING;
                }
                if ((data.getFlags() & FLAG_COMPRESSED) != 0)
                {
                    //byte[] decompress = new byte[Long.valueOf(data.getBlockSize()).intValue()];
                    //DECOMPRESS: MISSING ALGORITHM
                    //out.write(decompress, 0, data.getBlockSize());
                }
                else
                {
                    o.Write(decryptedData, 0, realLen);
                }
            }
            return(STATUS_OK);
        }
        private int encryptData(FileStream ii, FileStream o, NPD npd, EDATData data, byte[] rifkey)
        {
            int numBlocks = (int)((data.getFileLen() + data.getBlockSize() - 1) / data.getBlockSize());

            byte[] expectedHashForFile  = new byte[numBlocks * 0x10];
            byte[] encryptedDataForFile = new byte[ii.Length + 0xF];

            // File Format:
            // ALL HASHES
            // Encrypted Data
            for (int i = 0; i < numBlocks; i++)
            {
                long offset;
                int  len;

                offset = i * data.getBlockSize();
                ii.Seek(offset, SeekOrigin.Begin);
                len = (int)(data.getBlockSize());
                if (i == numBlocks - 1)
                {
                    len = (int)(data.getFileLen() % (new BigInteger(data.getBlockSize())));
                }


                int realLen = len;
                len = (int)((uint)(len + 0x0F) & 0xFFFFFFF0);

                byte[] encryptedData = new byte[len];
                byte[] decryptedData = new byte[len];

                int toRead = realLen;
                while (toRead > 0)
                {
                    toRead -= ii.Read(decryptedData, realLen - toRead, toRead);
                }
                for (int ai = realLen; ai < len; ai++)
                {
                    decryptedData[ai] = 0x00;
                }

                byte[] key      = new byte[0x10];
                byte[] hash     = new byte[0x10];
                byte[] blockKey = calculateBlockKey(i, npd);

                ToolsImpl.aesecbEncrypt(rifkey, blockKey, 0, key, 0, blockKey.Length);
                ConversionUtils.arraycopy(key, 0, hash, 0, key.Length);
                int cryptoFlag = 0x2;
                int hashFlag   = 0x02;

                AppLoaderReverse a             = new AppLoaderReverse();
                byte[]           iv            = npd.getDigest();
                byte[]           generatedHash = new byte[0x10];
                a.doAll(hashFlag, cryptoFlag, decryptedData, 0, encryptedData, 0, decryptedData.Length, key, iv, hash, generatedHash, 0);
                ConversionUtils.arraycopy(encryptedData, 0, encryptedDataForFile, offset, len);
                ConversionUtils.arraycopy(generatedHash, 0, expectedHashForFile, i * 0x10, 0x10);
            }
            byte[] EDATAVersion = ConversionUtils.getByteArray("4D6164652062792052325220546F6F6C");
            o.Write(expectedHashForFile, 0, expectedHashForFile.Length);
            o.Write(encryptedDataForFile, 0, encryptedDataForFile.Length - 0xf);
            o.Write(EDATAVersion, 0, EDATAVersion.Length);
            return(STATUS_OK);
        }