static void WriteEeprom(FamicomDumperConnection dumper, string fileName) { var nesFile = new NesFile(fileName); var prg = new byte[0x8000]; int s = 0; while (s < prg.Length) { var n = Math.Min(nesFile.PRG.Count(), prg.Length - s); Array.Copy(nesFile.PRG.ToArray(), s % nesFile.PRG.Count(), prg, s, n); s += n; } var chr = new byte[0x2000]; s = 0; while (s < chr.Length) { var n = Math.Min(nesFile.CHR.Count(), chr.Length - s); Array.Copy(nesFile.CHR.ToArray(), s % nesFile.CHR.Count(), chr, s, n); s += n; } dumper.Timeout = 1000; var buff = new byte[64]; Console.Write("Writing PRG EEPROM"); for (UInt16 a = 0; a < prg.Length; a += 64) { Array.Copy(prg, a, buff, 0, buff.Length); dumper.WriteCpu((UInt16)(0x8000 + a), buff); Thread.Sleep(3); Console.Write("."); } Console.WriteLine(" OK"); Console.Write("Writing CHR EEPROM"); for (UInt16 a = 0; a < chr.Length; a += 64) { Array.Copy(chr, a, buff, 0, buff.Length); dumper.WritePpu(a, buff); Thread.Sleep(3); Console.Write("."); } Console.WriteLine(" OK"); }
public static void Write(FamicomDumperConnection dumper, string fileName, IEnumerable <int> badSectors, bool silent, bool needCheck = false, bool writePBBs = false, bool ignoreBadSectors = false) { byte[] PRG; if (Path.GetExtension(fileName).ToLower() == ".bin") { PRG = File.ReadAllBytes(fileName); } else { try { var nesFile = new NesFile(fileName); PRG = nesFile.PRG.ToArray(); } catch { var nesFile = new UnifFile(fileName); PRG = nesFile.Fields["PRG0"]; } } int banks = PRG.Length / BANK_SIZE; Program.Reset(dumper); dumper.WriteCpu(0x5007, 0x04); // enable PRG write dumper.WriteCpu(0x5002, 0xFE); // mask = 32K dumper.WriteCpu(0x5000, 0); dumper.WriteCpu(0x5001, 0); FlashHelper.ResetFlash(dumper); var cfi = FlashHelper.GetCFIInfo(dumper); Console.WriteLine($"Device size: {cfi.DeviceSize / 1024 / 1024} MByte / {cfi.DeviceSize / 1024 / 1024 * 8} Mbit"); Console.WriteLine($"Maximum number of bytes in multi-byte program: {cfi.MaximumNumberOfBytesInMultiProgram}"); if (dumper.ProtocolVersion >= 3) { dumper.SetMaximumNumberOfBytesInMultiProgram(cfi.MaximumNumberOfBytesInMultiProgram); } FlashHelper.LockBitsCheckPrint(dumper); if (PRG.Length > cfi.DeviceSize) { throw new ArgumentOutOfRangeException("PRG.Length", "This ROM is too big for this cartridge"); } try { PPBClear(dumper); } catch (Exception ex) { if (!silent) { Program.PlayErrorSound(); } Console.WriteLine($"ERROR! {ex.Message}. Lets continue anyway."); } var writeStartTime = DateTime.Now; var lastSectorTime = DateTime.Now; var timeEstimated = new TimeSpan(); int totalErrorCount = 0; int currentErrorCount = 0; var newBadSectorsList = new List <int>(); for (int bank = 0; bank < banks; bank++) { while (badSectors.Contains(bank / 4) || newBadSectorsList.Contains(bank / 4)) { bank += 4; // bad sector :( } try { byte r0 = (byte)(bank >> 7); byte r1 = (byte)(bank << 1); dumper.WriteCpu(0x5000, r0); dumper.WriteCpu(0x5001, r1); var data = new byte[BANK_SIZE]; int pos = bank * BANK_SIZE; if (pos % (128 * 1024) == 0) { timeEstimated = new TimeSpan((DateTime.Now - lastSectorTime).Ticks * (banks - bank) / 4); timeEstimated = timeEstimated.Add(DateTime.Now - writeStartTime); lastSectorTime = DateTime.Now; Console.Write($"Erasing sector #{bank / 4}... "); dumper.EraseCpuFlashSector(); Console.WriteLine("OK"); } Array.Copy(PRG, pos, data, 0, data.Length); var timePassed = DateTime.Now - writeStartTime; Console.Write($"Writing bank #{bank}/{banks} ({100 * bank / banks}%, {timePassed.Hours:D2}:{timePassed.Minutes:D2}:{timePassed.Seconds:D2}/{timeEstimated.Hours:D2}:{timeEstimated.Minutes:D2}:{timeEstimated.Seconds:D2})... "); dumper.WriteCpuFlash(0x0000, data); Console.WriteLine("OK"); if ((bank % 4 == 3) || (bank == banks - 1)) // After last bank in sector { if (writePBBs) { FlashHelper.PPBSet(dumper); } currentErrorCount = 0; } } catch (Exception ex) { totalErrorCount++; currentErrorCount++; Console.WriteLine($"ERROR {ex.GetType()}: {ex.Message}"); if (!silent) { Program.PlayErrorSound(); } if (currentErrorCount >= 5) { if (!ignoreBadSectors) { throw ex; } else { newBadSectorsList.Add(bank / 4); currentErrorCount = 0; Console.WriteLine($"Lets skip sector #{bank / 4}"); } } else { Console.WriteLine("Lets try again"); } bank = (bank & ~3) - 1; Program.Reset(dumper); dumper.WriteCpu(0x5007, 0x04); // enable PRG write dumper.WriteCpu(0x5002, 0xFE); // mask = 32K FlashHelper.ResetFlash(dumper); continue; } } if (totalErrorCount > 0) { Console.WriteLine($"Write error count: {totalErrorCount}"); } if (newBadSectorsList.Any()) { Console.WriteLine($"Can't write sectors: {string.Join(", ", newBadSectorsList.OrderBy(s => s))}"); } var wrongCrcSectorsList = new List <int>(); if (needCheck) { Console.WriteLine("Starting verification process"); Program.Reset(dumper); var readStartTime = DateTime.Now; lastSectorTime = DateTime.Now; timeEstimated = new TimeSpan(); dumper.WriteCpu(0x5002, 0xFE); // mask = 32K for (int bank = 0; bank < banks; bank++) { while (badSectors.Contains(bank / 4)) { bank += 4; // bad sector :( } byte r0 = (byte)(bank >> 7); byte r1 = (byte)(bank << 1); dumper.WriteCpu(0x5000, r0); dumper.WriteCpu(0x5001, r1); var data = new byte[BANK_SIZE]; int pos = bank * BANK_SIZE; if (pos % (128 * 1024) == 0) { timeEstimated = new TimeSpan((DateTime.Now - lastSectorTime).Ticks * (banks - bank) / 4); timeEstimated = timeEstimated.Add(DateTime.Now - readStartTime); lastSectorTime = DateTime.Now; } Array.Copy(PRG, pos, data, 0, data.Length); ushort crc = 0; foreach (var a in data) { crc ^= a; for (int i = 0; i < 8; ++i) { if ((crc & 1) != 0) { crc = (ushort)((crc >> 1) ^ 0xA001); } else { crc = (ushort)(crc >> 1); } } } var timePassed = DateTime.Now - readStartTime; Console.Write($"Reading CRC of bank #{bank}/{banks} ({100 * bank / banks}%, {timePassed.Hours:D2}:{timePassed.Minutes:D2}:{timePassed.Seconds:D2}/{timeEstimated.Hours:D2}:{timeEstimated.Minutes:D2}:{timeEstimated.Seconds:D2})... "); var crcr = dumper.ReadCpuCrc(0x8000, BANK_SIZE); if (crcr != crc) { Console.WriteLine($"Verification failed: {crcr:X4} != {crc:X4}"); if (!silent) { Program.PlayErrorSound(); } wrongCrcSectorsList.Add(bank / 4); } else { Console.WriteLine($"OK (CRC = {crcr:X4})"); } } if (totalErrorCount > 0) { Console.WriteLine($"Write error count: {totalErrorCount}"); } if (newBadSectorsList.Any()) { Console.WriteLine($"Can't write sectors: {string.Join(", ", newBadSectorsList.OrderBy(s => s))}"); } if (wrongCrcSectorsList.Any()) { Console.WriteLine($"Sectors with wrong CRC: {string.Join(", ", wrongCrcSectorsList.Distinct().OrderBy(s => s))}"); } } if (newBadSectorsList.Any() || wrongCrcSectorsList.Any()) { throw new IOException("Cartridge is not writed correctly"); } }
public static void Write(IFamicomDumperConnectionExt dumper, string fileName, IEnumerable <int> badSectors, bool silent, bool needCheck = false, bool writePBBs = false, bool ignoreBadSectors = false) { byte[] PRG; if (Path.GetExtension(fileName).ToLower() == ".bin") { PRG = File.ReadAllBytes(fileName); } else { try { var nesFile = new NesFile(fileName); PRG = nesFile.PRG.ToArray(); } catch { var nesFile = new UnifFile(fileName); PRG = nesFile.Fields["PRG0"]; } } int banks = PRG.Length / BANK_SIZE; Program.Reset(dumper); var version = DetectVersion(dumper); var coolboyReg = (ushort)(version == 2 ? 0x5000 : 0x6000); FlashHelper.ResetFlash(dumper); var cfi = FlashHelper.GetCFIInfo(dumper); Console.WriteLine($"Device size: {cfi.DeviceSize / 1024 / 1024} MByte / {cfi.DeviceSize / 1024 / 1024 * 8} Mbit"); Console.WriteLine($"Maximum number of bytes in multi-byte program: {cfi.MaximumNumberOfBytesInMultiProgram}"); if (dumper.ProtocolVersion >= 3) { dumper.SetMaximumNumberOfBytesInMultiProgram(cfi.MaximumNumberOfBytesInMultiProgram); } FlashHelper.LockBitsCheckPrint(dumper); if (PRG.Length > cfi.DeviceSize) { throw new ArgumentOutOfRangeException("PRG.Length", "This ROM is too big for this cartridge"); } try { PPBClear(dumper, coolboyReg); } catch (Exception ex) { if (!silent) { Program.PlayErrorSound(); } Console.WriteLine($"ERROR! {ex.Message}. Lets continue anyway."); } var writeStartTime = DateTime.Now; var lastSectorTime = DateTime.Now; var timeEstimated = new TimeSpan(); int totalErrorCount = 0; int currentErrorCount = 0; var newBadSectorsList = new List <int>(badSectors); for (int bank = 0; bank < banks; bank++) { while (badSectors.Contains(bank / 8) || newBadSectorsList.Contains(bank / 8)) { bank += 8; // bad sector :( } try { 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, r0, r1, r2, r3); var data = new byte[BANK_SIZE]; int pos = bank * BANK_SIZE; if (pos % (128 * 1024) == 0) { timeEstimated = new TimeSpan((DateTime.Now - lastSectorTime).Ticks * (banks - bank) / 8); timeEstimated = timeEstimated.Add(DateTime.Now - writeStartTime); lastSectorTime = DateTime.Now; Console.Write($"Erasing sector #{bank / 8}... "); dumper.EraseFlashSector(); Console.WriteLine("OK"); } Array.Copy(PRG, pos, data, 0, data.Length); var timePassed = DateTime.Now - writeStartTime; Console.Write($"Writing bank #{bank}/{banks} ({100 * bank / banks}%, {timePassed.Hours:D2}:{timePassed.Minutes:D2}:{timePassed.Seconds:D2}/{timeEstimated.Hours:D2}:{timeEstimated.Minutes:D2}:{timeEstimated.Seconds:D2})... "); dumper.WriteFlash(0x0000, data); Console.WriteLine("OK"); if ((bank % 8 == 7) || (bank == banks - 1)) // After last bank in sector { if (writePBBs) { FlashHelper.PPBSet(dumper); } currentErrorCount = 0; } } catch (Exception ex) { totalErrorCount++; currentErrorCount++; Console.WriteLine($"Error {ex.GetType()}: {ex.Message}"); if (!silent) { Program.PlayErrorSound(); } if (currentErrorCount >= 5) { if (!ignoreBadSectors) { throw; } else { newBadSectorsList.Add(bank / 8); currentErrorCount = 0; Console.WriteLine($"Lets skip sector #{bank / 8}"); } } else { Console.WriteLine("Lets try again"); } bank = (bank & ~7) - 1; Program.Reset(dumper); FlashHelper.ResetFlash(dumper); continue; } } if (totalErrorCount > 0) { Console.WriteLine($"Write error count: {totalErrorCount}"); } if (newBadSectorsList.Any()) { Console.WriteLine($"Can't write sectors: {string.Join(", ", newBadSectorsList.OrderBy(s => s))}"); } var wrongCrcSectorsList = new List <int>(); if (needCheck) { Console.WriteLine("Starting verification process"); Program.Reset(dumper); var readStartTime = DateTime.Now; lastSectorTime = DateTime.Now; timeEstimated = new TimeSpan(); for (int bank = 0; bank < banks; bank++) { while (badSectors.Contains(bank / 8)) { bank += 8; // bad sector :( } 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, r0, r1, r2, r3); int pos = bank * BANK_SIZE; if (pos % (128 * 1024) == 0) { timeEstimated = new TimeSpan((DateTime.Now - lastSectorTime).Ticks * (banks - bank) / 8); timeEstimated = timeEstimated.Add(DateTime.Now - readStartTime); lastSectorTime = DateTime.Now; } ushort crc = Crc16Calculator.CalculateCRC16(PRG, pos, BANK_SIZE); var timePassed = DateTime.Now - readStartTime; Console.Write($"Reading CRC of bank #{bank}/{banks} ({100 * bank / banks}%, {timePassed.Hours:D2}:{timePassed.Minutes:D2}:{timePassed.Seconds:D2}/{timeEstimated.Hours:D2}:{timeEstimated.Minutes:D2}:{timeEstimated.Seconds:D2})... "); var crcr = dumper.ReadCpuCrc(0x8000, BANK_SIZE); if (crcr != crc) { Console.WriteLine($"Verification failed: {crcr:X4} != {crc:X4}"); if (!silent) { Program.PlayErrorSound(); } wrongCrcSectorsList.Add(bank / 8); } else { Console.WriteLine($"OK (CRC = {crcr:X4})"); } } if (totalErrorCount > 0) { Console.WriteLine($"Write error count: {totalErrorCount}"); } if (newBadSectorsList.Any()) { Console.WriteLine($"Can't write sectors: {string.Join(", ", newBadSectorsList.OrderBy(s => s))}"); } if (wrongCrcSectorsList.Any()) { Console.WriteLine($"Sectors with wrong CRC: {string.Join(", ", wrongCrcSectorsList.Distinct().OrderBy(s => s))}"); } } if (newBadSectorsList.Any() || wrongCrcSectorsList.Any()) { throw new IOException("Cartridge is not writed correctly"); } }
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"); }