Esempio n. 1
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            dumper.WriteCpu((ushort)(0x5000), (byte)0x00);
            dumper.WriteCpu((ushort)(0x5001), (byte)0x10);
            dumper.WriteCpu((ushort)(0x5002), (byte)0x10);
            dumper.WriteCpu((ushort)(0x5006), (byte)0x07);
            dumper.WriteCpu((ushort)(0x5007), (byte)0x03);
            byte banks = (byte)(size / 0x8000);

            for (int bank = 0; bank < banks; bank++)
            {
                Console.Write("Reading PRG bank #{0}... ", bank);
                // TODO: избежать конфликтов шины
                // Avoiding bus conflicts

                /*
                 * for (int i = 0; i < lastBank.Length; i++)
                 * {
                 *  if (lastBank[i] == bank)
                 *  {
                 *      break;
                 *  }
                 * }
                 */
                dumper.WriteCpu((ushort)(0x8000), (byte)bank);
                data.AddRange(dumper.ReadCpu(0x8000, 0x8000));
                Console.WriteLine("OK");
            }
        }
Esempio n. 2
0
 public static void PPBClear(FamicomDumperConnection dumper)
 {
     LockBitsCheckPrint(dumper);
     PPBLockBitCheckPrint(dumper);
     Console.Write($"Erasing all PBBs... ");
     // PPB Command Set Entry
     dumper.WriteCpu(0x8AAA, 0xAA);
     dumper.WriteCpu(0x8555, 0x55);
     dumper.WriteCpu(0x8AAA, 0xC0);
     // All PPB Erase
     dumper.WriteCpu(0x8000, 0x80);
     dumper.WriteCpu(0x8000, 0x30);
     // Check
     try
     {
         DateTime startTime = DateTime.Now;
         while (true)
         {
             byte b = dumper.ReadCpu(0x8000);
             if (b == 0x01)
             {
                 break;
             }
             if ((DateTime.Now - startTime).TotalMilliseconds >= 1500)
             {
                 throw new IOException("PPB clear failed");
             }
         }
     }
     finally
     {
         ResetFlash(dumper);
     }
     Console.WriteLine("OK");
 }
        public void DumpChr(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            byte outbanks = (byte)(size / (256 * 1024));

            for (byte outbank = 0; outbank < outbanks; outbank += 1)
            {
                dumper.Reset();
                dumper.WriteCpu(0xA001, 0x80); // RAM protect
                dumper.WriteCpu((ushort)(0x6828 | (outbank << 1)), 0x00);
                dumper.WriteCpu(0xA001, 0);    // disable W-RAM

                int banks = 256;
                if (banks > 256)
                {
                    throw new Exception("CHR size is too big");
                }
                for (int bank = 0; bank < banks; bank += 4)
                {
                    Console.Write("Reading CHR banks #{4}|{0}, #{4}|{1}, #{4}|{2}, #{4}|{3}... ", bank, bank + 1, bank + 2, bank + 3, outbank);
                    dumper.WriteCpu(0x8000, new byte[] { 2, (byte)bank });
                    dumper.WriteCpu(0x8000, new byte[] { 3, (byte)(bank | 1) });
                    dumper.WriteCpu(0x8000, new byte[] { 4, (byte)(bank | 2) });
                    dumper.WriteCpu(0x8000, new byte[] { 5, (byte)(bank | 3) });
                    data.AddRange(dumper.ReadPpu(0x1000, 0x1000));
                    Console.WriteLine("OK");
                }
            }
        }
Esempio n. 4
0
        static void StartServer(FamicomDumperConnection dumper, uint tcpPort)
        {
            BinaryServerFormatterSinkProvider binaryServerFormatterSinkProvider
                = new BinaryServerFormatterSinkProvider();
            BinaryClientFormatterSinkProvider binaryClientFormatterSinkProvider
                = new BinaryClientFormatterSinkProvider();

            binaryServerFormatterSinkProvider.TypeFilterLevel
                = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
            var dict = new System.Collections.Hashtable();

            dict["name"]   = "FamicomDumperServer";
            dict["port"]   = tcpPort;
            dict["secure"] = false;
            var channel = new TcpChannel(dict, binaryClientFormatterSinkProvider, binaryServerFormatterSinkProvider);

            ChannelServices.RegisterChannel(channel, false);
            dumper.Verbose = true;
            RemotingServices.Marshal(dumper, "dumper");
            Console.WriteLine($"Listening port {tcpPort}, press any key to stop");
            Console.ReadKey();
            Console.WriteLine();
            ChannelServices.UnregisterChannel(channel);
            channel.StopListening(null);
        }
Esempio n. 5
0
 public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
 {
     //dumper.WritePrg((ushort)(0x5001), (byte)0x02);
     Console.Write("Reading PRG... ");
     data.AddRange(dumper.ReadCpu(0x8000, size));
     Console.WriteLine("OK");
 }
Esempio n. 6
0
        static void TestChrRam(FamicomDumperConnection dumper)
        {
            var rnd = new Random();

            while (true)
            {
                var data = new byte[0x2000];
                rnd.NextBytes(data);
                Console.Write("Writing CHR RAM... ");
                dumper.WritePpu(0x0000, data);
                Console.Write("Reading CHR RAM... ");
                var  rdata = dumper.ReadPpu(0x0000, 0x2000);
                bool ok    = true;
                for (int b = 0; b < 0x2000; b++)
                {
                    if (data[b] != rdata[b])
                    {
                        Console.WriteLine($"Mismatch at {b:X4}: {rdata[b]:X2} != {data[b]:X2}");
                        ok = false;
                    }
                }
                if (!ok)
                {
                    File.WriteAllBytes("chrramgood.bin", data);
                    Console.WriteLine("chrramgood.bin writed");
                    File.WriteAllBytes("chrrambad.bin", rdata);
                    Console.WriteLine("chrrambad.bin writed");
                    throw new VerificationException("Failed!");
                }
                Console.WriteLine("OK!");
            }
        }
 public void EnablePrgRam(FamicomDumperConnection dumper)
 {
     dumper.Reset();
     dumper.WriteCpu(0xA001, 0x00);
     dumper.WriteCpu(0x6003, 0x80);
     dumper.WriteCpu(0xA001, 0x80);
 }
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            dumper.Reset();
            int outbanks = size / (128 * 1024);

            int outbankSize = 512;

            for (int outbank = 0; outbank < outbanks; outbank += outbankSize / 128)
            {
                byte r0 = (byte)((outbank & 0x07) | ((outbank & 0xc0) >> 2));
                byte r1 = (byte)(((outbank & 0x30) >> 2) | ((outbank << 1) & 0x10));
                byte r2 = 0;
                byte r3 = 0;
                dumper.WriteCpu(0x6000, new byte[] { r0 });
                dumper.WriteCpu(0x6001, new byte[] { r1 });
                dumper.WriteCpu(0x6002, new byte[] { r2 });
                dumper.WriteCpu(0x6003, new byte[] { r3 });

                int banks = outbankSize * 1024 / 0x2000;
                for (int bank = 0; bank < banks - 2; bank += 2)
                {
                    Console.Write("Reading PRG banks #{2}|{0} and #{2}|{1}... ", bank, bank + 1, outbank);
                    dumper.WriteCpu(0x8000, new byte[] { 6, (byte)(bank) });
                    dumper.WriteCpu(0x8000, new byte[] { 7, (byte)(bank | 1) });
                    data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                    Console.WriteLine("OK");
                }
                Console.Write("Reading last PRG banks #{2}|{0} and #{2}|{1}... ", banks - 2, banks - 1, outbank);
                data.AddRange(dumper.ReadCpu(0xC000, 0x4000));
                Console.WriteLine("OK");
            }
        }
Esempio n. 9
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            byte banks = (byte)(size / 0x4000);

            Console.Write("Reading last PRG bank... ");
            byte[] lastBank = dumper.ReadCpu(0xC000, 0x4000);
            Console.WriteLine("OK");
            for (int bank = 0; bank < banks - 1; bank++)
            {
                Console.Write("Reading PRG bank #{0}... ", bank);
                // Avoiding bus conflicts
                bool noBusConflict = false;
                for (int i = 0; i < lastBank.Length; i++)
                {
                    if (lastBank[i] == bank)
                    {
                        dumper.WriteCpu((ushort)(0xC000 + i), (byte)bank);
                        noBusConflict = true;
                        break;
                    }
                }
                if (!noBusConflict) // Whatever...
                {
                    dumper.WriteCpu((ushort)0x8000, (byte)bank);
                }
                data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                Console.WriteLine("OK");
            }
            data.AddRange(lastBank);
        }
Esempio n. 10
0
 public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
 {
     Console.Write("Dumping PRG... ");
     prg = dumper.ReadCpu(0x8000, size);
     data.AddRange(prg);
     Console.WriteLine("OK");
 }
Esempio n. 11
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            dumper.Reset();
            int banks = size / 0x4000;

            for (int bank = 0; bank < banks; bank++)
            {
                byte r0 = (byte)(((bank >> 3) & 0x07)          // 5, 4, 3 bits
                                 | (((bank >> 9) & 0x03) << 4) // 10, 9 bits
                                 | (1 << 6));                  // resets 4th mask bit
                byte r1 = (byte)((((bank >> 7) & 0x03) << 2)   // 8, 7
                                 | (((bank >> 6) & 1) << 4)    // 6
                                 | (1 << 7));                  // resets 5th mask bit
                byte r2 = 0;
                byte r3 = (byte)((1 << 4)                      // NROM mode
                                 | ((bank & 7) << 1));         // 2, 1, 0 bits
                dumper.WriteCpu(0x6000, new byte[] { r0 });
                dumper.WriteCpu(0x6001, new byte[] { r1 });
                dumper.WriteCpu(0x6002, new byte[] { r2 });
                dumper.WriteCpu(0x6003, new byte[] { r3 });

                Console.Write("Reading PRG banks #{0}/{1}... ", bank, banks);
                data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                Console.WriteLine("OK");
            }
        }
Esempio n. 12
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            //dumper.WritePrg(0xFFC9, 1);
            dumper.WriteCpu((ushort)(0x5000), (byte)0x00);
            dumper.WriteCpu((ushort)(0x5001), (byte)0x08);
            dumper.WriteCpu((ushort)(0x5002), (byte)0xF8);
            dumper.WriteCpu((ushort)(0x5003), (byte)0x00);
            dumper.WriteCpu((ushort)(0x5004), (byte)0x00);
            dumper.WriteCpu((ushort)(0x5005), (byte)0x00);
            dumper.WriteCpu((ushort)(0x5006), (byte)0x02);
            dumper.WriteCpu((ushort)(0x5007), (byte)0x82);

            byte banks = (byte)(size / 0x4000);

            Console.Write("Reading last PRG bank... ");
            byte[] lastBank = dumper.ReadCpu(0xC000, 0x4000);
            Console.WriteLine("OK");
            for (int bank = 0; bank < banks - 1; bank++)
            {
                Console.Write("Reading PRG bank #{0}... ", bank);
                // Avoiding bus conflicts
                for (int i = 0; i < lastBank.Length; i++)
                {
                    if (lastBank[i] == bank)
                    {
                        dumper.WriteCpu((ushort)(0xC000 + i), (byte)bank);
                        break;
                    }
                }
                data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                Console.WriteLine("OK");
            }
            data.AddRange(lastBank);
        }
Esempio n. 13
0
 public static void PPBSet(FamicomDumperConnection dumper)
 {
     Console.Write("Writing PPB for sector... ");
     // PPB Command Set Entry
     dumper.WriteCpu(0x8AAA, 0xAA);
     dumper.WriteCpu(0x8555, 0x55);
     dumper.WriteCpu(0x8AAA, 0xC0);
     // PPB Program
     dumper.WriteCpu(0x8000, 0xA0);
     dumper.WriteCpu(0x8000, 0x00);
     // Check
     try
     {
         DateTime startTime = DateTime.Now;
         while (true)
         {
             byte b = dumper.ReadCpu(0x8000);
             if (b == 0x00)
             {
                 break;
             }
             if ((DateTime.Now - startTime).TotalMilliseconds >= 1500)
             {
                 throw new IOException("PPB write failed");
             }
         }
     }
     finally
     {
         ResetFlash(dumper);
     }
     Console.WriteLine("OK");
 }
Esempio n. 14
0
 public static void LockBitsCheckPrint(FamicomDumperConnection dumper)
 {
     try
     {
         // Lock Register Set Entry
         dumper.WriteCpu(0x8AAA, 0xAA);
         dumper.WriteCpu(0x8555, 0x55);
         dumper.WriteCpu(0x8AAA, 0x40);
         var lockRegister = dumper.ReadCpu(0x8000);
         if ((lockRegister & 1) == 0)
         {
             Console.WriteLine("WARNING: Secured Silicon Sector Protection Bit is set!");
         }
         if ((lockRegister & 2) == 0)
         {
             Console.WriteLine("WARNING: Persistent Protection Mode Lock Bit is set!");
         }
         if ((lockRegister & 4) == 0)
         {
             Console.WriteLine("WARNING: Password Protection Mode Lock Bit is set!");
         }
     }
     finally
     {
         ResetFlash(dumper);
     }
 }
Esempio n. 15
0
        private static void CheckRAMAdapter(FamicomDumperConnection dumper)
        {
            // Just simple test that RAM adapter is connected
            bool ramAdapterPresent = true;

            dumper.WriteCpu(0x4023, 0x01);       // enable disk registers
            dumper.WriteCpu(0x4026, 0x00);
            dumper.WriteCpu(0x4025, 0b00100110); // reset
            dumper.WriteCpu(0x0000, 0xFF);       // to prevent open bus read
            var ext = dumper.ReadCpu(0x4033);

            if (ext != 0x00)
            {
                ramAdapterPresent = false;
            }
            dumper.WriteCpu(0x4026, 0xFF);
            dumper.WriteCpu(0x0000, 0x00); // to prevent open bus read
            ext = dumper.ReadCpu(0x4033);
            if ((ext & 0x7F) != 0x7F)
            {
                ramAdapterPresent = false;
            }
            if (!ramAdapterPresent)
            {
                throw new IOException("RAM adapter IO error, is it connected?");
            }
        }
Esempio n. 16
0
 public static void PasswordUnlock(FamicomDumperConnection dumper, byte[] password)
 {
     try
     {
         if (password.Length != 8)
         {
             throw new InvalidDataException("Invalid password length");
         }
         Console.Write("Unlocking password... ");
         // Password Protection Set Entry
         dumper.WriteCpu(0x8AAA, 0xAA);
         dumper.WriteCpu(0x8555, 0x55);
         dumper.WriteCpu(0x8AAA, 0x60);
         // Password unlock
         dumper.WriteCpu(0x8000, 0x25);
         dumper.WriteCpu(0x8000, 0x03);
         for (byte i = 0; i < password.Length; i++)
         {
             dumper.WriteCpu((ushort)(0x8000 + i), password[i]);
         }
         dumper.WriteCpu(0x8000, 0x29);
         Console.WriteLine("OK");
     }
     finally
     {
         ResetFlash(dumper);
     }
 }
Esempio n. 17
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            dumper.Reset();
            version = CoolboyWriter.DetectVersion(dumper);
            UInt16 coolboyReg = (UInt16)(version == 2 ? 0x5000 : 0x6000);
            int    banks      = size / 0x4000;

            for (int bank = 0; bank < banks; bank++)
            {
                byte r0 = (byte)(((bank >> 3) & 0x07)          // 5, 4, 3 bits
                                 | (((bank >> 9) & 0x03) << 4) // 10, 9 bits
                                 | (1 << 6));                  // resets 4th mask bit
                byte r1 = (byte)((((bank >> 7) & 0x03) << 2)   // 8, 7
                                 | (((bank >> 6) & 1) << 4)    // 6
                                 | (1 << 7));                  // resets 5th mask bit
                byte r2 = 0;
                byte r3 = (byte)((1 << 4)                      // NROM mode
                                 | ((bank & 7) << 1));         // 2, 1, 0 bits
                dumper.WriteCpu(coolboyReg, new byte[] { r0, r1, r2, r3 });

                Console.Write("Reading PRG banks #{0}/{1}... ", bank, banks);
                data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                Console.WriteLine("OK");
            }
        }
Esempio n. 18
0
 public void DumpChr(FamicomDumperConnection dumper, List <byte> data, int size = 0)
 {
     this.dumper = dumper;
     resultChr.Clear();
     script.Call(script.Globals["DumpChr"], size);
     data.AddRange(resultChr);
 }
Esempio n. 19
0
 public static void ResetFlash(FamicomDumperConnection dumper)
 {
     // Exit command set entry if any
     dumper.WriteCpu(0x8000, 0x90);
     dumper.WriteCpu(0x8000, 0x00);
     // Reset
     dumper.WriteCpu(0x8000, 0xF0);
 }
Esempio n. 20
0
 void WriteMMC1(FamicomDumperConnection dumper, UInt16 address, byte data)
 {
     byte[] buffer = new byte[5];
     for (int i = 0; i < 5; i++)
     {
         buffer[i] = (byte)(data >> i);
     }
     dumper.WriteCpu(address, buffer);
 }
Esempio n. 21
0
        public static void PasswordProgramm(FamicomDumperConnection dumper, byte[] password)
        {
            if (password.Length != 8)
            {
                throw new InvalidDataException("Invalid password length");
            }
            Console.Write("Programming password... ");
            // Password Protection Set Entry
            dumper.WriteCpu(0x8AAA, 0xAA);
            dumper.WriteCpu(0x8555, 0x55);
            dumper.WriteCpu(0x8AAA, 0x60);
            try
            {
                for (byte i = 0; i < password.Length; i++)
                {
                    dumper.WriteCpu(0x8000, 0xA0);
                    dumper.WriteCpu((ushort)(0x8000 + i), password[i]);
                }
                var verify = dumper.ReadCpu(0x8000, 8);
                for (byte i = 0; i < password.Length; i++)
                {
                    if (password[i] != verify[i])
                    {
                        throw new InvalidDataException("Password verification failed");
                    }
                }
            }
            finally
            {
                ResetFlash(dumper);
            }
            Console.WriteLine("OK");

            Console.Write("Programming lock register... ");
            // Lock Register Set Entry
            dumper.WriteCpu(0x8AAA, 0xAA);
            dumper.WriteCpu(0x8555, 0x55);
            dumper.WriteCpu(0x8AAA, 0x40);
            try
            {
                // Bits Program
                dumper.WriteCpu(0x8000, 0xA0);
                dumper.WriteCpu(0x8000, (byte)(1 << 2) ^ 0xFF); // password protection
                var r = dumper.ReadCpu(0x8000);
                if ((r & 7) != 3)
                {
                    throw new InvalidDataException("Lock bit verification failed");
                }
            }
            finally
            {
                ResetFlash(dumper);
            }
            Console.WriteLine("OK");
        }
Esempio n. 22
0
        public static void PringFlashInfo(FamicomDumperConnection dumper)
        {
            Program.Reset(dumper);
            dumper.WriteCpu(0x5007, 0x04); // enable PRG write
            dumper.WriteCpu(0x5002, 0xFE); // mask = 32K
            var cfi = FlashHelper.GetCFIInfo(dumper);

            FlashHelper.PrintCFIInfo(cfi);
            FlashHelper.LockBitsCheckPrint(dumper);
            FlashHelper.PPBLockBitCheckPrint(dumper);
        }
Esempio n. 23
0
 public void Execute(FamicomDumperConnection dumper, string scriptSource, bool useFile = true)
 {
     this.dumper = dumper;
     if (useFile)
     {
         script.DoFile(scriptSource);
     }
     else
     {
         script.DoString(scriptSource);
     }
 }
Esempio n. 24
0
 public static void FullTest(FamicomDumperConnection dumper, int count = -1, int chrSize = -1)
 {
     while (count != 0)
     {
         TestChrRam(dumper, 1, chrSize);
         TestPrgRam(dumper, 1);
         if (count > 0)
         {
             count--;
         }
     }
 }
Esempio n. 25
0
        public static void PPBClear(FamicomDumperConnection dumper)
        {
            // enable PRG write
            dumper.WriteCpu(0x5007, 0x04);
            // mask = 32K
            dumper.WriteCpu(0x5002, 0xFE);
            // Sector 0
            dumper.WriteCpu(0x5000, 0);
            dumper.WriteCpu(0x5001, 0);

            FlashHelper.PPBClear(dumper);
        }
Esempio n. 26
0
        static NesFile.MirroringType GetMirroring(FamicomDumperConnection dumper, IMapper mapper)
        {
            var method = mapper.GetType().GetMethod(
                "GetMirroring", BindingFlags.Instance | BindingFlags.Public,
                null, CallingConventions.Any, new Type[] { typeof(IFamicomDumperConnection) },
                new ParameterModifier[0]);

            if (method == null)
            {
                return(dumper.GetMirroring());
            }
            return((NesFile.MirroringType)method.Invoke(mapper, new object[] { dumper }));
        }
Esempio n. 27
0
        public void DumpChr(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            dumper.WriteCpu(0x8000, 0x80);
            WriteMMC1(dumper, 0x8000, 0x0C);

            byte banks = (byte)(size / 0x1000);

            for (int bank = 0; bank < banks; bank += 2)
            {
                Console.Write("Reading CHR banks #{0} and #{1}... ", bank, bank + 1);
                WriteMMC1(dumper, 0xA000, (byte)bank);
                data.AddRange(dumper.ReadPpu(0x0000, 0x2000));
                Console.WriteLine("OK");
            }
        }
Esempio n. 28
0
        public void DumpPrg(FamicomDumperConnection dumper, List <byte> data, int size)
        {
            byte banks = (byte)(size / 0x2000);

            for (byte bank = 0; bank < banks - 2; bank += 2)
            {
                Console.Write("Reading PRG banks #{0} and #{1}... ", bank, bank + 1);
                dumper.WriteCpu(0x8000, new byte[] { 6, bank });
                dumper.WriteCpu(0x8000, new byte[] { 7, (byte)(bank | 1) });
                data.AddRange(dumper.ReadCpu(0x8000, 0x4000));
                Console.WriteLine("OK");
            }
            Console.Write("Reading last PRG banks #{0} and #{1}... ", banks - 2, banks - 1);
            data.AddRange(dumper.ReadCpu(0xC000, 0x4000));
            Console.WriteLine("OK");
        }
Esempio n. 29
0
 public static byte PPBRead(FamicomDumperConnection dumper)
 {
     try
     {
         // PPB Command Set Entry
         dumper.WriteCpu(0x8AAA, 0xAA);
         dumper.WriteCpu(0x8555, 0x55);
         dumper.WriteCpu(0x8AAA, 0xC0);
         // PPB Status Read
         return(dumper.ReadCpu(0x8000));
     }
     finally
     {
         ResetFlash(dumper);
     }
 }
Esempio n. 30
0
        public static void DumpFDS(FamicomDumperConnection dumper, string fileName, byte sides = 1, bool dumpHiddenFiles = true, bool useHeader = true)
        {
            if (dumper.ProtocolVersion < 3)
            {
                throw new NotSupportedException("Dumper firmware version is too old, update it to read/write FDS cards");
            }
            CheckRAMAdapter(dumper);

            var sideImages = new List <FdsDiskSide>();

            for (int side = 1; side <= sides; side++)
            {
                var driveStatus = dumper.ReadCpu(0x4032);
                if ((driveStatus & 1) != 0)
                {
                    Console.Write($"Please set disk card, side #{side}... ");
                    while ((driveStatus & 1) != 0)
                    {
                        Thread.Sleep(100);
                        driveStatus = dumper.ReadCpu(0x4032);
                    }
                    Console.WriteLine("OK");
                }
                var sideImage = DumpFDSSide(dumper, dumpHiddenFiles, printDiskInfo: true);
                sideImages.Add(sideImage);

                if (side < sides)
                {
                    driveStatus = dumper.ReadCpu(0x4032);
                    if ((driveStatus & 1) == 0)
                    {
                        Console.Write($"Please remove disk card... ");
                        while ((driveStatus & 1) == 0)
                        {
                            Thread.Sleep(100);
                            driveStatus = dumper.ReadCpu(0x4032);
                        }
                        Console.WriteLine("OK");
                    }
                }
            }
            Console.Write($"Saving to {fileName}... ");
            var fdsImage = new FdsFile(sideImages);

            fdsImage.Save(fileName, useHeader);
            Console.WriteLine("OK");
        }