public static int GetFlashSizePrintInfo(FamicomDumperConnection dumper)
        {
            dumper.WriteCpu(0x8AAA, 0x98); // CFI mode
            var cfi = dumper.ReadCpu(0x8000, 0x200);

            dumper.WriteCpu(0x8000, 0xF0); // Reset
            if (cfi[0x20] != 0x51 || cfi[0x22] != 0x52 || cfi[0x24] != 0x59)
            {
                throw new Exception("Can't enter CFI mode. Invalid flash memory? Broken cartridge? Is it inserted?");
            }
            int size = 1 << cfi[0x27 * 2];
            FlashDeviceInterface flashDeviceInterface = (FlashDeviceInterface)(cfi[0x28 * 2] | (cfi[0x29 * 2] << 8));

            Console.WriteLine("Primary Algorithm Command Set and Control Interface ID Code: {0:X2}{1:X2}h", cfi[0x13 * 2], cfi[0x14 * 2]);
            Console.WriteLine("Vcc Logic Supply Minimum Program / Erase voltage: {0}v", (cfi[0x1B * 2] >> 4) + 0.1 * (cfi[0x1B * 2] & 0x0F));
            Console.WriteLine("Vcc Logic Supply Maximum Program / Erase voltage: {0}v", (cfi[0x1C * 2] >> 4) + 0.1 * (cfi[0x1C * 2] & 0x0F));
            Console.WriteLine("Vpp [Programming] Supply Minimum Program / Erase voltage: {0}v", (cfi[0x1D * 2] >> 4) + 0.1 * (cfi[0x1D * 2] & 0x0F));
            Console.WriteLine("Vpp [Programming] Supply Maximum Program / Erase voltage: {0}v", (cfi[0x1E * 2] >> 4) + 0.1 * (cfi[0x1E * 2] & 0x0F));
            Console.WriteLine("Maximum number of bytes in multi-byte program: {0}", 1 << (cfi[0x2A * 2] | (cfi[0x2B * 2] << 8)));
            Console.WriteLine("Device size: {0} MByte / {1} Mbit", size / 1024 / 1024, size / 1024 / 1024 * 8);
            Console.WriteLine("Flash device interface: {0}", flashDeviceInterface.ToString().Replace("_", " "));
            return(size);
        }
        public CFIInfo(byte[] data, ParseMode parseMode)
        {
            switch (parseMode)
            {
            case ParseMode.Every2Bytes:
            {
                var newData = new byte[data.Length / 2];
                for (int i = 0; i < newData.Length; i++)
                {
                    newData[i] = data[i * 2];
                }
                data = newData;
                break;
            }

            case ParseMode.Every4Bytes:
            {
                var newData = new byte[data.Length / 4];
                for (int i = 0; i < newData.Length; i++)
                {
                    newData[i] = data[i * 4];
                }
                data = newData;
                break;
            }
            }
            if (data[0x10] != 0x51 || data[0x11] != 0x52 || data[0x12] != 0x59)
            {
                throw new IOException("Can't enter CFI mode. Invalid flash memory? Broken cartridge? Is it inserted?");
            }

            PrimaryAlgorithmCommandSet = (ushort)(data[0x13] + data[0x14] * 0x100);
            //var p = (ushort)(data[0x15] + data[0x16] * 0x100);
            AlternativeAlgorithmCommandSet = (ushort)(data[0x17] + data[0x18] * 0x100);
            //var a = (ushort)(data[0x19] + data[0x20] * 0x100);
            VccLogicSupplyMinimumProgramErase            = (float)((data[0x1B] >> 4) + 0.1 * (data[0x1B] & 0x0F));
            VccLogicSupplyMaximumProgramErase            = (float)((data[0x1C] >> 4) + 0.1 * (data[0x1C] & 0x0F));
            VppSupplyMinimumProgramErasevoltage          = (float)((data[0x1D] >> 4) + 0.1 * (data[0x1D] & 0x0F));
            VppSupplyMaximumProgramErasevoltage          = (float)((data[0x1E] >> 4) + 0.1 * (data[0x1E] & 0x0F));
            TypicalTimeoutPerSingleProgram               = data[0x1F] == 0 ? 0 : (1U << data[0x1F]);
            TypicalTimeoutForMaximumSizeMultiByteProgram = data[0x20] == 0 ? 0 : (1U << data[0x20]);
            TypicalTimeoutPerIndividualBlockErase        = data[0x21] == 0 ? 0 : (1U << data[0x21]);
            TypicalTimeoutForFullChipErase               = data[0x22] == 0 ? 0 : (1U << data[0x22]);
            MaximumTimeoutPerSingleProgram               = data[0x1F] == 0 ? 0 : ((1U << data[0x1F]) * (1U << data[0x23]));
            MaximumTimeoutForMaximumSizeMultiByteProgram = data[0x20] == 0 ? 0 : ((1U << data[0x20]) * (1U << data[0x24]));
            MaximumTimeoutPerIndividualBlockErase        = data[0x21] == 0 ? 0 : ((1U << data[0x21]) * (1U << data[0x25]));
            MaximumTimeoutForFullChipErase               = data[0x22] == 0 ? 0 : ((1U << data[0x22]) * (1U << data[0x26]));
            DeviceSize = 1U << data[0x27];
            FlashDeviceInterfaceCodeDescription = (FlashDeviceInterface)(data[0x28] + data[0x29] * 0x100);
            MaximumNumberOfBytesInMultiProgram  = (ushort)(1U << (data[0x2A] + data[0x2B] * 0x100));
            var eraseBlockRegions = data[0x2C];
            var regions           = new List <EraseBlockRegionInfo>();

            for (int i = 0; i < eraseBlockRegions; i++)
            {
                ushort numberOfBlocks = (ushort)(data[0x2D + i * 4] + data[0x2E + i * 4] * 0x100 + 1);
                uint   sizeOfBlocks   = (ushort)(data[0x2F + i * 4] + data[0x30 + i * 4] * 0x100);
                sizeOfBlocks = sizeOfBlocks == 0 ? 128 : (256 * sizeOfBlocks);
                regions.Add(new EraseBlockRegionInfo(numberOfBlocks, sizeOfBlocks));
            }
            EraseBlockRegionsInfo = regions.AsReadOnly();
        }