static void Dump(FamicomDumperConnection dumper, string fileName, string mapperName, int prgSize, int chrSize, string unifName, string unifAuthor)
        {
            var mapper = GetMapper(mapperName);

            if (mapper.Number >= 0)
            {
                Console.WriteLine("Using mapper: #{0} ({1})", mapper.Number, mapper.Name);
            }
            else
            {
                Console.WriteLine("Using mapper: {0}", mapper.Name);
            }
            Console.WriteLine("Dumping...");
            List <byte> prg = new List <byte>();
            List <byte> chr = new List <byte>();

            prgSize = prgSize >= 0 ? prgSize : mapper.DefaultPrgSize;
            chrSize = chrSize >= 0 ? chrSize : mapper.DefaultChrSize;
            Console.WriteLine("PRG memory size: {0}K", prgSize / 1024);
            mapper.DumpPrg(dumper, prg, prgSize);
            while (prg.Count % 0x4000 != 0)
            {
                prg.Add(0);
            }
            Console.WriteLine("CHR memory size: {0}K", chrSize / 1024);
            mapper.DumpChr(dumper, chr, chrSize);
            while (chr.Count % 0x2000 != 0)
            {
                chr.Add(0);
            }
            byte[] mirroringRaw             = dumper.GetMirroring();
            NesFile.MirroringType mirroring = NesFile.MirroringType.Unknown_none;
            if (mirroringRaw.Length == 1)
            {
                mirroring = (NesFile.MirroringType)mirroringRaw[0];
                Console.WriteLine("Mirroring: " + mirroring);
            }
            else if (mirroringRaw.Length == 4)
            {
                switch (string.Format("{0:X2}{1:X2}{2:X2}{3:X2}", mirroringRaw[0], mirroringRaw[1], mirroringRaw[2], mirroringRaw[3]))
                {
                case "00000101":
                    mirroring = NesFile.MirroringType.Horizontal;     // Horizontal
                    break;

                case "00010001":
                    mirroring = NesFile.MirroringType.Vertical;     // Vertical
                    break;

                case "00000000":
                    mirroring = NesFile.MirroringType.OneScreenA;     // One-screen A
                    break;

                case "01010101":
                    mirroring = NesFile.MirroringType.OneScreenB;     // One-screen B
                    break;

                default:
                    mirroring = NesFile.MirroringType.Unknown_none;     // Unknown
                    break;
                }
                Console.WriteLine("Mirroring: {0} ({1:X2} {2:X2} {3:X2} {4:X2})", mirroring, mirroringRaw[0], mirroringRaw[1], mirroringRaw[2], mirroringRaw[3]);
            }
            Console.WriteLine("Saving to {0}...", fileName);
            if (mapper.Number >= 0)
            {
                var nesFile = new NesFile();
                nesFile.Mapper    = (byte)mapper.Number;
                nesFile.Mirroring = mirroring;
                nesFile.TvSystem  = NesFile.TvSystemType.Ntsc;
                nesFile.PRG       = prg.ToArray();
                nesFile.CHR       = chr.ToArray();
                nesFile.Save(fileName);
            }
            else
            {
                var unifFile = new UnifFile();
                unifFile.Version = 4;
                unifFile.Mapper  = mapper.UnifName;
                if (unifName != null)
                {
                    unifFile.Fields["NAME"] = UnifFile.StringToUTF8N(unifName);
                }
                unifFile.Fields["PRG0"] = prg.ToArray();
                if (chr.Count > 0)
                {
                    unifFile.Fields["CHR0"] = chr.ToArray();
                }
                unifFile.Fields["MIRR"] = new byte[] { 5 }; // mapper controlled
                unifFile.Save(fileName, unifAuthor);
            }
        }
        /// <summary>
        /// Fix ROM header using database of popular incorrect ROMs
        /// </summary>
        /// <returns>Flags showing what has been changed</returns>
        public static NesFixType CorrectRom(this NesFile nes)
        {
            NesFixType fixType = NesFixType.NoFix;
            var        crc32   = nes.CalculateCRC32();

            for (int i = 0; i < correct.GetLength(0); i++)
            {
                if (crc32 == correct[i, 0])
                {
                    var mapper    = correct[i, 1] & 0x3FF;
                    var mask      = ((correct[i, 1] & 0x1000) != 0) ? 0xFFF : 0xFF;
                    var mirroring = correct[i, 2];
                    if ((correct[i, 1] >= 0) && (mapper >= 0) && (nes.Mapper != (mapper & mask)))
                    {
                        // invalid mapper
                        nes.Mapper = (ushort)(mapper & mask);
                        fixType   |= NesFixType.Mapper;
                    }
                    if (mirroring >= 0)
                    {
                        if (mirroring == 8 && nes.Mirroring == NesFile.MirroringType.FourScreenVram)
                        {
                            // no four-screen
                            nes.Mirroring = NesFile.MirroringType.Horizontal;
                            fixType      |= NesFixType.Mirroring;
                        }
                        NesFile.MirroringType needMirroring = NesFile.MirroringType.Unknown;
                        switch (mirroring)
                        {
                        case 0:
                            needMirroring = NesFile.MirroringType.Horizontal;
                            break;

                        case 1:
                            needMirroring = NesFile.MirroringType.Vertical;
                            break;

                        case 2:
                            needMirroring = NesFile.MirroringType.FourScreenVram;
                            break;
                        }
                        if (needMirroring != NesFile.MirroringType.Unknown && needMirroring != nes.Mirroring)
                        {
                            nes.Mirroring = needMirroring;
                            fixType      |= NesFixType.Mirroring;
                        }
                    }
                    if ((correct[i, 1] >= 0) && ((correct[i, 1] & 0x800) != 0) && (nes.CHR.Count() > 0))
                    {
                        // no CHR
                        nes.CHR  = Array.Empty <byte>();
                        fixType |= NesFixType.NoChr;
                    }
                }
            }

            var   md5        = nes.CalculateMD5();
            ulong partialmd5 = 0;

            for (int x = 0; x < 8; x++)
            {
                partialmd5 |= (ulong)md5[15 - x] << (x * 8);
            }
            // maybe this games uses battery saves?
            foreach (var sav in savie)
            {
                if (!nes.Battery && sav == partialmd5)
                {
                    nes.Battery = true;
                    fixType    |= NesFixType.Battery;
                }
            }

            return(fixType);
        }
        static void Dump(FamicomDumperConnection dumper, string fileName, string mapperName, int prgSize, int chrSize, string unifName, string unifAuthor, bool battery)
        {
            var mapper = GetMapper(mapperName);

            if (mapper.Number >= 0)
            {
                Console.WriteLine($"Using mapper: #{mapper.Number} ({mapper.Name})");
            }
            else
            {
                Console.WriteLine($"Using mapper: {mapper.Name}");
            }
            Console.WriteLine("Dumping...");
            List <byte> prg = new List <byte>();
            List <byte> chr = new List <byte>();

            prgSize = prgSize >= 0 ? prgSize : mapper.DefaultPrgSize;
            chrSize = chrSize >= 0 ? chrSize : mapper.DefaultChrSize;
            if (prgSize > 0)
            {
                Console.WriteLine($"PRG memory size: {prgSize / 1024}KB");
                mapper.DumpPrg(dumper, prg, prgSize);
                while (prg.Count % 0x4000 != 0)
                {
                    prg.Add(0);
                }
            }
            if (chrSize > 0)
            {
                Console.WriteLine($"CHR memory size: {chrSize / 1024}KB");
                mapper.DumpChr(dumper, chr, chrSize);
                while (chr.Count % 0x2000 != 0)
                {
                    chr.Add(0);
                }
            }
            NesFile.MirroringType mirroring = NesFile.MirroringType.Unknown;
            // TODO: move GetMapper to IMapper, so it will not be optional
            //mirroring = mapper.GetMirroring(dumper);
            mirroring = GetMirroring(dumper, mapper);
            Console.WriteLine($"Mirroring: {mirroring}");
            Console.Write($"Saving to {fileName}... ");
            if (mapper.Number >= 0)
            {
                // TODO: add RAM and NV-RAM settings for NES 2.0
                var nesFile   = new NesFile();
                var submapper = GetSubmapper(mapper);
                nesFile.Version = (mapper.Number > 255 || submapper != 0)
                    ? NesFile.iNesVersion.NES20
                    : NesFile.iNesVersion.iNES;
                nesFile.Mapper    = (ushort)mapper.Number;
                nesFile.Submapper = submapper;
                nesFile.Mirroring = mirroring;
                nesFile.PRG       = prg.ToArray();
                nesFile.CHR       = chr.ToArray();
                nesFile.Battery   = battery;
                nesFile.Save(fileName);
            }
            else
            {
                var unifFile = new UnifFile();
                unifFile.Version = 4;
                unifFile.Mapper  = mapper.UnifName;
                if (unifName != null)
                {
                    unifFile.GameName = unifName;
                }
                unifFile.Fields["PRG0"] = prg.ToArray();
                if (chr.Count > 0)
                {
                    unifFile.Fields["CHR0"] = chr.ToArray();
                }
                unifFile.Mirroring = mirroring;
                unifFile.Battery   = battery;
                if (!string.IsNullOrEmpty(unifAuthor))
                {
                    unifFile.DumperName = unifAuthor;
                }
                unifFile.DumpingSoftware = "Famicom Dumper by Cluster / https://github.com/ClusterM/famicom-dumper-client";
                unifFile.Save(fileName);
            }
            Console.WriteLine("OK");
        }