public byte[] GetBytes(FileStream isoStream, long volumeOffset,
                               long dataOffset, long blockOffset, long size, byte[] partitionKey)
        {
            byte[] value = new byte[size];
            byte[] encryptedPartitionCluster;
            byte[] encryptedClusterDataSection;
            byte[] decryptedClusterDataSection;
            byte[] clusterIV;

            long bufferLocation = 0;
            long maxCopySize;
            long copySize;

            while (size > 0)
            {
                // get block offset info
                NintendoWiiOpticalDisc.WiiBlockStructure blockStructure =
                    NintendoWiiOpticalDisc.GetWiiBlockStructureForOffset(blockOffset);

                // read current block
                //encryptedPartitionCluster = ParseFile.ParseSimpleOffset(isoStream,
                //    volumeOffset + dataOffset + (blockStructure.BlockNumber * 0x8000),
                //    0x8000);

                if (this.CurrentDecryptedBlockNumber != blockStructure.BlockNumber)
                {
                    encryptedPartitionCluster = ParseFile.ParseSimpleOffset(isoStream,
                                                                            volumeOffset + dataOffset + (blockStructure.BlockNumber * 0x8000),
                                                                            0x8000);

                    clusterIV = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x03D0, 0x10);
                    encryptedClusterDataSection = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x400, 0x7C00);
                    decryptedClusterDataSection = AESEngine.Decrypt(this.Algorithm, encryptedClusterDataSection,
                                                                    partitionKey, clusterIV, CipherMode.CBC, PaddingMode.Zeros);

                    this.CurrentDecryptedBlock       = decryptedClusterDataSection;
                    this.CurrentDecryptedBlockNumber = blockStructure.BlockNumber;
                }

                // copy the decrypted data
                maxCopySize = 0x7C00 - blockStructure.BlockOffset;
                copySize    = (size > maxCopySize) ? maxCopySize : size;
                Array.Copy(this.CurrentDecryptedBlock, blockStructure.BlockOffset,
                           value, bufferLocation, copySize);

                // update counters
                size           -= copySize;
                bufferLocation += copySize;
                blockOffset    += copySize;
            }

            return(value);
        }
        public void ExtractFile(FileStream isoStream, string destinationPath, long volumeOffset,
                                long dataOffset, long blockOffset, long size, byte[] partitionKey)
        {
            byte[] value = new byte[0x7C00];
            byte[] encryptedPartitionCluster;
            byte[] encryptedClusterDataSection;
            byte[] decryptedClusterDataSection;
            byte[] clusterIV;

            long maxCopySize;
            long copySize;

            // create destination
            string destintionDirectory = Path.GetDirectoryName(destinationPath);

            if (!Directory.Exists(destintionDirectory))
            {
                Directory.CreateDirectory(destintionDirectory);
            }

            using (FileStream outStream = File.Open(destinationPath, FileMode.Create, FileAccess.Write))
            {
                while (size > 0)
                {
                    try
                    {
                        // get block offset info
                        NintendoWiiOpticalDisc.WiiBlockStructure blockStructure =
                            NintendoWiiOpticalDisc.GetWiiBlockStructureForOffset(blockOffset);

                        // read current block
                        //encryptedPartitionCluster = ParseFile.ParseSimpleOffset(isoStream,
                        //    volumeOffset + dataOffset + (blockStructure.BlockNumber * 0x8000),
                        //    0x8000);

                        if (this.CurrentDecryptedBlockNumber != blockStructure.BlockNumber)
                        {
                            encryptedPartitionCluster = ParseFile.ParseSimpleOffset(isoStream,
                                                                                    volumeOffset + dataOffset + (blockStructure.BlockNumber * 0x8000),
                                                                                    0x8000);

                            if (encryptedPartitionCluster.Length == 0)
                            {
                                throw new Exception(String.Format("Encrypted cluster 0x{0) has size zero.  This image is probably corrupt.", blockStructure.BlockNumber.ToString("X8")));
                            }
                            else
                            {
                                clusterIV = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x03D0, 0x10);
                                encryptedClusterDataSection = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x400, 0x7C00);
                                decryptedClusterDataSection = AESEngine.Decrypt(this.Algorithm, encryptedClusterDataSection,
                                                                                partitionKey, clusterIV, CipherMode.CBC, PaddingMode.Zeros);

                                this.CurrentDecryptedBlock       = decryptedClusterDataSection;
                                this.CurrentDecryptedBlockNumber = blockStructure.BlockNumber;
                            }
                        }

                        // copy the encrypted data
                        maxCopySize = 0x7C00 - blockStructure.BlockOffset;
                        copySize    = (size > maxCopySize) ? maxCopySize : size;
                        outStream.Write(this.CurrentDecryptedBlock, (int)blockStructure.BlockOffset, (int)copySize);

                        // update counters
                        size        -= copySize;
                        blockOffset += copySize;
                    }
                    catch (Exception ie)
                    {
                        throw ie;
                    }
                }
            }
        }
        public void InitializePartitions(FileStream isoStream)
        {
            //byte[] encryptedPartitionCluster;
            //byte[] decryptedClusterDataSection;
            //byte[] clusterIV;
            //byte[] encryptedClusterDataSection;

            this.CommonKey       = null;
            this.KoreanCommonKey = null;
            this.Partitions      = new Partition[4];

            for (int i = 0; i < 4; i++)
            {
                this.Partitions[i] = new Partition();
                this.Partitions[i].PartitionCount   = ByteConversion.GetUInt32BigEndian(ParseFile.ParseSimpleOffset(isoStream, this.DiscBaseOffset + 0x40000 + (i * 8), 4));
                this.Partitions[i].PartitionEntries = new PartitionEntry[this.Partitions[i].PartitionCount];

                this.Partitions[i].PartitionTableOffset   = ByteConversion.GetUInt32BigEndian(ParseFile.ParseSimpleOffset(isoStream, this.DiscBaseOffset + 0x40004 + (i * 8), 4));
                this.Partitions[i].PartitionTableOffset <<= 2;

                if (this.Partitions[i].PartitionTableOffset > 0)
                {
                    // set absolute offset of partition
                    this.Partitions[i].PartitionTableOffset += this.DiscBaseOffset;

                    for (int j = 0; j < this.Partitions[i].PartitionCount; j++)
                    {
                        this.Partitions[i].PartitionEntries[j] = new PartitionEntry();

                        // get offset to this partition
                        this.Partitions[i].PartitionEntries[j].PartitionOffset =
                            ByteConversion.GetUInt32BigEndian(ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionTableOffset + (j * 8), 4));
                        this.Partitions[i].PartitionEntries[j].PartitionOffset <<= 2;
                        this.Partitions[i].PartitionEntries[j].PartitionOffset  += this.DiscBaseOffset;

                        // get partition type
                        this.Partitions[i].PartitionEntries[j].PartitionType =
                            ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionTableOffset + 4 + (i * 8), 4);

                        // get relative offset partition's data section
                        this.Partitions[i].PartitionEntries[j].RelativeDataOffset   = ByteConversion.GetUInt32BigEndian(ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionEntries[j].PartitionOffset + 0x02B8, 4));
                        this.Partitions[i].PartitionEntries[j].RelativeDataOffset <<= 2;
                        this.Partitions[i].PartitionEntries[j].RelativeDataOffset  += this.DiscBaseOffset;

                        // get the size of partition's data section
                        this.Partitions[i].PartitionEntries[j].DataSize   = ByteConversion.GetUInt32BigEndian(ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionEntries[j].PartitionOffset + 0x02BC, 4));
                        this.Partitions[i].PartitionEntries[j].DataSize <<= 2;

                        //---------------------------
                        // parse this entry's ticket
                        //---------------------------
                        this.Partitions[i].PartitionEntries[j].TitleId = new byte[0x10];
                        Array.Copy(
                            ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionEntries[j].PartitionOffset + 0x01DC, 8),
                            0,
                            this.Partitions[i].PartitionEntries[j].TitleId,
                            0,
                            8);

                        this.Partitions[i].PartitionEntries[j].EncryptedTitleKey = ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionEntries[j].PartitionOffset + 0x01BF, 0x10);
                        this.Partitions[i].PartitionEntries[j].CommonKeyIndex    = ParseFile.ParseSimpleOffset(isoStream, this.Partitions[i].PartitionEntries[j].PartitionOffset + 0x01F1, 1)[0];

                        //---------------------
                        // decrypt the TitleId
                        //---------------------
                        switch (this.Partitions[i].PartitionEntries[j].CommonKeyIndex)
                        {
                        case 0:
                            if (this.CommonKey == null)
                            {
                                this.CommonKey = NintendoWiiOpticalDisc.GetKeyFromFile(NintendoWiiOpticalDisc.COMMON_KEY_PATH);
                            }

                            this.Partitions[i].PartitionEntries[j].DecryptedTitleKey =
                                AESEngine.Decrypt(this.Partitions[i].PartitionEntries[j].EncryptedTitleKey,
                                                  this.CommonKey, this.Partitions[i].PartitionEntries[j].TitleId,
                                                  CipherMode.CBC, PaddingMode.Zeros);

                            break;

                        case 1:
                            if (this.KoreanCommonKey == null)
                            {
                                this.KoreanCommonKey = NintendoWiiOpticalDisc.GetKeyFromFile(NintendoWiiOpticalDisc.KOREAN_KEY_PATH);
                            }

                            this.Partitions[i].PartitionEntries[j].DecryptedTitleKey =
                                AESEngine.Decrypt(this.Partitions[i].PartitionEntries[j].EncryptedTitleKey,
                                                  this.KoreanCommonKey, this.Partitions[i].PartitionEntries[j].TitleId,
                                                  CipherMode.CBC, PaddingMode.Zeros);

                            break;
                        } // switch (this.Partitions[i].PartitionEntries[j].CommonKeyIndex)


                        //string outFile = Path.Combine(Path.GetDirectoryName(isoStream.Name), String.Format("{0}-{1}.bin", i.ToString(), j.ToString()));
                        //long currentOffset = 0;

                        //using (FileStream outStream = File.OpenWrite(outFile))
                        //{

                        //    while (currentOffset < this.Partitions[i].PartitionEntries[j].DataSize)
                        //    {
                        //        encryptedPartitionCluster = ParseFile.ParseSimpleOffset(isoStream,
                        //            this.Partitions[i].PartitionEntries[j].PartitionOffset + this.Partitions[i].PartitionEntries[j].RelativeDataOffset + currentOffset,
                        //            0x8000);

                        //        clusterIV = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x03D0, 0x10);
                        //        encryptedClusterDataSection = ParseFile.ParseSimpleOffset(encryptedPartitionCluster, 0x400, 0x7C00);
                        //        decryptedClusterDataSection = AESEngine.Decrypt(encryptedClusterDataSection,
                        //                        this.Partitions[i].PartitionEntries[j].DecryptedTitleKey, clusterIV,
                        //                        CipherMode.CBC, PaddingMode.Zeros);

                        //        outStream.Write(decryptedClusterDataSection, 0, decryptedClusterDataSection.Length);

                        //        currentOffset += 0x8000;
                        //    }
                        //}
                    } // for (int j = 0; j < this.Partitions[i].PartitionCount; j++)
                }     // if (this.Partitions[i].PartitionTableOffset > 0)
            }
        }