Esempio n. 1
0
        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");
            }
        }
        static int Main(string[] args)
        {
            var mappers = new Dictionary<byte, MapperInfo>();
            mappers[0] = new MapperInfo { MapperReg = 0x00, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // NROM
            mappers[2] = new MapperInfo { MapperReg = 0x01, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // UxROM
            mappers[71] = new MapperInfo { MapperReg = 0x01, PrgMode = 0, ChrMode = 0, WramEnabled = false, MapperFlags = 1 }; // Codemasters
            mappers[3] = new MapperInfo { MapperReg = 0x02, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // CNROM
            mappers[78] = new MapperInfo { MapperReg = 0x03, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // Holy Diver
            mappers[97] = new MapperInfo { MapperReg = 0x04, PrgMode = 1, ChrMode = 0, WramEnabled = false }; // Irem's TAM-S1
            mappers[93] = new MapperInfo { MapperReg = 0x05, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // Sunsoft-2
            mappers[163] = new MapperInfo { MapperReg = 0x06, PrgMode = 7, ChrMode = 0, WramEnabled = true }; // Mapper 163 (Final Fantasy & chinese shit)
            mappers[18] = new MapperInfo { MapperReg = 0x07, PrgMode = 4, ChrMode = 7, WramEnabled = false }; // Jaleco SS88006
            mappers[7] = new MapperInfo { MapperReg = 0x08, PrgMode = 7, ChrMode = 0, WramEnabled = false }; // AxROM
            mappers[241] = new MapperInfo { MapperReg = 0x08, PrgMode = 7, ChrMode = 0, WramEnabled = false, MapperFlags = 1 }; // BNROM - is it just AxROM clone whith fixed mirroring? Using flag.
            mappers[228] = new MapperInfo { MapperReg = 0x09, PrgMode = 7, ChrMode = 0, WramEnabled = false }; // Cheetahmen 2
            mappers[11] = new MapperInfo { MapperReg = 0x0A, PrgMode = 7, ChrMode = 0, WramEnabled = false }; // Color Dreams
            mappers[66] = new MapperInfo { MapperReg = 0x0B, PrgMode = 7, ChrMode = 0, WramEnabled = false }; // GxROM
            mappers[87] = new MapperInfo { MapperReg = 0x0C, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // Mapper #87
            mappers[90] = new MapperInfo { MapperReg = 0x0D, PrgMode = 4, ChrMode = 7, WramEnabled = false }; // Mapper #90
            mappers[65] = new MapperInfo { MapperReg = 0x0E, PrgMode = 4, ChrMode = 7, WramEnabled = false }; // Mapper #65 - Irem's H3001
            mappers[5] = new MapperInfo { MapperReg = 0x0F, PrgMode = 4, ChrMode = 7, WramEnabled = true }; // MMC5
            mappers[1] = new MapperInfo { MapperReg = 0x10, PrgMode = 0, ChrMode = 0, WramEnabled = true }; // MMC1
            mappers[9] = new MapperInfo { MapperReg = 0x11, PrgMode = 4, ChrMode = 5, WramEnabled = true }; // MMC2
            mappers[10] = new MapperInfo { MapperReg = 0x11, PrgMode = 0, ChrMode = 5, WramEnabled = true, MapperFlags = 1 }; // MMC4
            mappers[152] = new MapperInfo { MapperReg = 0x12, PrgMode = 0, ChrMode = 0, WramEnabled = false }; // Mapper #152
            mappers[73] = new MapperInfo { MapperReg = 0x13, PrgMode = 0, ChrMode = 0, WramEnabled = true }; // VRC3
            mappers[4] = new MapperInfo { MapperReg = 0x14, PrgMode = 4, ChrMode = 2, WramEnabled = true }; // MMC3
            mappers[118] = new MapperInfo { MapperReg = 0x14, PrgMode = 4, ChrMode = 2, WramEnabled = true, MapperFlags = 1 }; // TxSROM (MMC3 with flag)
            mappers[189] = new MapperInfo { MapperReg = 0x14, PrgMode = 7, ChrMode = 2, WramEnabled = false, MapperFlags = 2 }; // Mapper #189
            mappers[112] = new MapperInfo { MapperReg = 0x15, PrgMode = 4, ChrMode = 2, WramEnabled = true }; // Mapper #112
            mappers[4] = new MapperInfo { MapperReg = 0x14, PrgMode = 4, ChrMode = 2, WramEnabled = true }; // MMC3
            mappers[33] = new MapperInfo { MapperReg = 0x16, PrgMode = 4, ChrMode = 2, WramEnabled = true }; // Taito
            mappers[48] = new MapperInfo { MapperReg = 0x16, PrgMode = 4, ChrMode = 2, WramEnabled = true, MapperFlags = 1 }; // Taito
            mappers[21] = new MapperInfo { MapperReg = 0x18, PrgMode = 4, ChrMode = 7, WramEnabled = true, MapperFlags = 1 }; // VRC4a
            mappers[22] = new MapperInfo { MapperReg = 0x18, PrgMode = 4, ChrMode = 7, WramEnabled = true, MapperFlags = 1 | 2 }; // VRC2a
            mappers[23] = new MapperInfo { MapperReg = 0x18, PrgMode = 4, ChrMode = 7, WramEnabled = true }; // VRC2b
            mappers[25] = new MapperInfo { MapperReg = 0x18, PrgMode = 4, ChrMode = 7, WramEnabled = true, MapperFlags = 1 }; // VRC2c, VRC4
            mappers[69] = new MapperInfo { MapperReg = 0x19, PrgMode = 4, ChrMode = 7, WramEnabled = true }; // Sunsoft FME-7
            mappers[32] = new MapperInfo { MapperReg = 0x1A, PrgMode = 4, ChrMode = 7, WramEnabled = true }; // Irem's G-101
            //mappers[255] = new MapperInfo { MapperReg = 0x1F, PrgMode = 7, ChrMode = 0, WramEnabled = false }; // Temp/test
            Console.WriteLine("COOLGIRL UNIF combiner");
            Console.WriteLine("(c) Cluster, 2016");
            Console.WriteLine("http://clusterrr.com");
            Console.WriteLine("*****@*****.**");
            Console.WriteLine();
            bool needShowHelp = false;

            string command = null;
            string optionGames = null;
            string optionAsm = null;
            string optionOffsets = null;
            string optionReport = null;
            string optionLoader = null;
            string optionUnif = null;
            string optionBin = null;
            string optionLanguage = "eng";
            bool optionNoSort = false;
            int optionMaxSize = 256;
            if (args.Length > 0) command = args[0].ToLower();
            if (command != "prepare" && command != "combine")
            {
                if (!string.IsNullOrEmpty(command))
                    Console.WriteLine("Unknown command: " + command);
                needShowHelp = true;
            }
            for (int i = 1; i < args.Length; i++)
            {
                string param = args[i];
                while (param.StartsWith("-")) param = param.Substring(1);
                string value = i < args.Length - 1 ? args[i + 1] : "";
                switch (param.ToLower())
                {
                    case "games":
                        optionGames = value;
                        i++;
                        break;
                    case "asm":
                        optionAsm = value;
                        i++;
                        break;
                    case "offsets":
                        optionOffsets = value;
                        i++;
                        break;
                    case "report":
                        optionReport = value;
                        i++;
                        break;
                    case "loader":
                        optionLoader = value;
                        i++;
                        break;
                    case "unif":
                        optionUnif = value;
                        i++;
                        break;
                    case "bin":
                        optionBin = value;
                        i++;
                        break;
                    case "nosort":
                        optionNoSort = true;
                        break;
                    case "maxsize":
                        optionMaxSize = int.Parse(value);
                        i++;
                        break;
                    case "language":
                        optionLanguage = value.ToLower();
                        i++;
                        break;
                    default:
                        Console.WriteLine("Unknown parameter: " + param);
                        needShowHelp = true;
                        break;
                }
            }

            if (command == "prepare")
            {
                if (optionGames == null)
                {
                    Console.WriteLine("Missing required parameter: --games");
                    needShowHelp = true;
                }
                if (optionAsm == null)
                {
                    Console.WriteLine("Missing required parameter: --asm");
                    needShowHelp = true;
                }
                if (optionOffsets == null)
                {
                    Console.WriteLine("Missing required parameter: --offsets");
                    needShowHelp = true;
                }
            }
            else if (command == "combine")
            {
                if (optionLoader == null)
                {
                    Console.WriteLine("Missing required parameter: --loader");
                    needShowHelp = true;
                }
                if (optionUnif == null && optionBin == null)
                {
                    Console.WriteLine("At least one parameter required: --unif or --bin");
                    needShowHelp = true;
                }
            }

            if (needShowHelp)
            {
                Console.WriteLine("");
                Console.WriteLine("--- Usage ---");
                Console.WriteLine("First step:");
                Console.WriteLine(" CoolgirlCombiner.exe prepare --games <games.txt> --asm <games.asm> --offsets <offsets.xml> [--report <report.txt>] [--nosort] [--maxsize sizemb] [--language <language>]");
                Console.WriteLine("  {0,-20}{1}", "games", "- input plain text file with list of ROM files");
                Console.WriteLine("  {0,-20}{1}", "asm", "- output file for loader");
                Console.WriteLine("  {0,-20}{1}", "offsets", "- output file with offsets for every game");
                Console.WriteLine("  {0,-20}{1}", "report", "- output report file (human readable)");
                Console.WriteLine("  {0,-20}{1}", "nosort", "- disable automatic sort by name");
                Console.WriteLine("  {0,-20}{1}", "maxsize", "- maximum size for final file (in megabytes)");
                Console.WriteLine("  {0,-20}{1}", "language", "- language for system messages: \"eng\" or \"rus\"");
                Console.WriteLine("Second step:");
                Console.WriteLine(" CoolgirlCombiner.exe combine --loader <menu.nes> --offsets <offsets.xml> [--unif <multirom.unf>] [--bin <multirom.bin>]");
                Console.WriteLine("  {0,-20}{1}", "loader", "- loader (compiled using asm file generated by first step)");
                Console.WriteLine("  {0,-20}{1}", "offsets", "- input file with offsets for every game (generated by first step)");
                Console.WriteLine("  {0,-20}{1}", "unif", "- output UNIF file");
                Console.WriteLine("  {0,-20}{1}", "bin", "- output raw binary file");
                return 1;
            }

            try
            {
                if (command == "prepare")
                {
                    var lines = File.ReadAllLines(optionGames);
                    var result = new byte?[256 * 1024 * 1024];
                    var regs = new Dictionary<string, List<String>>();
                    var games = new List<Game>();
                    var namesIncluded = new List<String>();

                    // Reserved for loader
                    for (int a = 0; a < 128 * 1024; a++)
                        result[a] = 0xff;

                    // Bad sector :(
                    // for (int a = 1908 * 0x8000; a < 1908 * 0x8000 + 128 * 1024; a++) result[a] = 0xff;

                    // Building list of ROMs
                    foreach (var line in lines)
                    {
                        if (string.IsNullOrEmpty(line.Trim())) continue;
                        if (line.StartsWith(";")) continue;
                        int sepPos = line.TrimEnd().IndexOf('|');

                        string fileName;
                        string menuName = null;
                        if (sepPos < 0)
                        {
                            fileName = line.Trim();
                        }
                        else
                        {
                            fileName = line.Substring(0, sepPos).Trim();
                            menuName = line.Substring(sepPos + 1).Trim();
                        }

                        if (fileName.EndsWith("/") || fileName.EndsWith("\\"))
                        {
                            Console.WriteLine("Loading directory: {0}", fileName);
                            var files = Directory.GetFiles(fileName, "*.nes");
                            foreach (var file in files)
                            {
                                LoadGames(games, file);
                            }
                        }
                        else
                        {
                            LoadGames(games, fileName, menuName);
                        }
                    }

                    // Removing separators
                    if (!optionNoSort)
                        games = new List<Game>((from game in games where !(string.IsNullOrEmpty(game.FileName) || game.FileName == "-") select game).ToArray());
                    // Sorting
                    if (optionNoSort)
                    {
                        games = new List<Game>((from game in games where !game.ToString().StartsWith("?") select game)
                        .Union(from game in games where game.ToString().StartsWith("?") orderby game.ToString().ToUpper() ascending select game)
                        .ToArray());
                    }
                    else
                    {
                        games = new List<Game>((from game in games where !game.ToString().StartsWith("?") orderby game.ToString().ToUpper() ascending select game)
                            .Union(from game in games where game.ToString().StartsWith("?") orderby game.ToString().ToUpper() ascending select game)
                            .ToArray());
                    }
                    int hiddenCount = games.Where(game => game.ToString().StartsWith("?")).Count();

                    byte saveId = 0;
                    foreach (var game in games)
                    {
                        if (game.ROM.Battery)
                        {
                            saveId++;
                            game.SaveId = saveId;
                        }
                    }

                    int usedSpace = 0;
                    var sortedPrgs = from game in games orderby game.ROM.PRG.Length descending select game;
                    foreach (var game in sortedPrgs)
                    {
                        int prgRoundSize = 1;
                        while (prgRoundSize < game.ROM.PRG.Length) prgRoundSize *= 2;

                        Console.WriteLine("Fitting PRG for {0} ({1}kbytes)...", game, prgRoundSize / 1024);
                        for (int pos = 0; pos < result.Length; pos += prgRoundSize)
                        {
                            if (WillFit(result, pos, prgRoundSize, game.ROM.PRG))
                            {
                                game.PrgPos = pos;
                                //Array.Copy(game.ROM.PRG, 0, result, pos, game.ROM.PRG.Length);
                                for (var i = 0; i < game.ROM.PRG.Length; i++)
                                    result[pos + i] = game.ROM.PRG[i];
                                usedSpace = Math.Max(usedSpace, pos + game.ROM.PRG.Length);
                                Console.WriteLine("Address: {0:X8}", pos);
                                break;
                            }
                        }
                        if (game.PrgPos < 0) throw new Exception("Can't fit " + game);
                    }

                    var sortedChrs = from game in games orderby game.ROM.CHR.Length descending select game;
                    foreach (var game in sortedChrs)
                    {
                        int chrSize = game.ROM.CHR.Length;
                        if (chrSize == 0) continue;

                        Console.WriteLine("Fitting CHR for {0} ({1}kbytes)...", game, chrSize / 1024);
                        for (int pos = 0; pos < result.Length; pos += 0x2000)
                        {
                            if (WillFit(result, pos, chrSize, game.ROM.CHR))
                            {
                                game.ChrPos = pos;
                                //Array.Copy(game.ROM.CHR, 0, result, pos, game.ROM.CHR.Length);
                                for (var i = 0; i < game.ROM.CHR.Length; i++)
                                    result[pos + i] = game.ROM.CHR[i];
                                usedSpace = Math.Max(usedSpace, pos + game.ROM.CHR.Length);
                                Console.WriteLine("Address: {0:X8}", pos);
                                break;
                            }
                        }
                        if (game.ChrPos < 0) throw new Exception("Can't fit " + game.FileName);
                    }
                    while (usedSpace % 0x8000 != 0)
                        usedSpace++;
                    int romSize = usedSpace;
                    usedSpace += 128 * 1024 * (int)Math.Ceiling(saveId / 4.0);

                    int totalSize = 0;
                    int maxChrSize = 0;
                    namesIncluded.Add(string.Format("{0,-33} {1,-10} {2,-10} {3,-10} {4,0}", "Game name", "Mapper", "Save ID", "Size", "Total size"));
                    namesIncluded.Add(string.Format("{0,-33} {1,-10} {2,-10} {3,-10} {4,0}", "------------", "-------", "-------", "-------", "--------------"));
                    var mapperStats = new Dictionary<byte, int>();
                    foreach (var game in games)
                    {
                        if (!game.ToString().StartsWith("?"))
                        {
                            totalSize += game.ROM.PRG.Length;
                            totalSize += game.ROM.CHR.Length;
                            namesIncluded.Add(string.Format("{0,-33} {1,-10} {2,-10} {3,-10} {4,0}", FirstCharToUpper(game.ToString().Replace("_", " ").Replace("+", "")), game.ROM.Mapper, game.SaveId == 0 ? "-" : game.SaveId.ToString(),
                                ((game.ROM.PRG.Length + game.ROM.CHR.Length) / 1024) + " KB", (totalSize / 1024) + " KB total"));
                            if (!mapperStats.ContainsKey(game.ROM.Mapper)) mapperStats[game.ROM.Mapper] = 0;
                            mapperStats[game.ROM.Mapper]++;
                        }
                        if (game.ROM.CHR.Length > maxChrSize)
                            maxChrSize = game.ROM.CHR.Length;
                    }
                    namesIncluded.Add("");
                    namesIncluded.Add(string.Format("{0,-10} {1,0}", "Mapper", "Count"));
                    namesIncluded.Add(string.Format("{0,-10} {1,0}", "------", "-----"));
                    foreach (var mapper in from m in mapperStats.Keys orderby m ascending select m)
                    {
                        namesIncluded.Add(string.Format("{0,-10} {1,0}", mapper, mapperStats[mapper]));
                    }

                    namesIncluded.Add("");
                    namesIncluded.Add("Total games: " + (games.Count - hiddenCount));
                    namesIncluded.Add("Final ROM size: " + Math.Round(usedSpace / 1024.0 / 1024.0, 3) + "MB");
                    namesIncluded.Add("Maximum CHR size: " + maxChrSize / 1024 + "KB");
                    namesIncluded.Add("Battery-backed games: " + saveId);

                    Console.WriteLine("Total games: " + (games.Count - hiddenCount));
                    Console.WriteLine("Final ROM size: " + Math.Round(usedSpace / 1024.0 / 1024.0, 3) + "MB");
                    Console.WriteLine("Maximum CHR size: " + maxChrSize / 1024 + "KB");
                    Console.WriteLine("Battery-backed games: " + saveId);

                    if (optionReport != null)
                        File.WriteAllLines(optionReport, namesIncluded.ToArray());

                    if (games.Count - hiddenCount == 0)
                        throw new Exception("Games list is empty");

                    if (usedSpace > optionMaxSize * 1024 * 1024)
                        throw new Exception(string.Format("ROM is too big: {0} MB", Math.Round(usedSpace / 1024.0 / 1024.0, 3)));
                    if (games.Count > 768)
                        throw new Exception("Too many ROMs: " + games.Count);
                    if (saveId > 128)
                        throw new Exception("Too many battery backed games: " + saveId);

                    regs["reg_0"] = new List<String>();
                    regs["reg_1"] = new List<String>();
                    regs["reg_2"] = new List<String>();
                    regs["reg_3"] = new List<String>();
                    regs["reg_4"] = new List<String>();
                    regs["reg_5"] = new List<String>();
                    regs["reg_6"] = new List<String>();
                    regs["reg_7"] = new List<String>();
                    regs["chr_start_bank_h"] = new List<String>();
                    regs["chr_start_bank_l"] = new List<String>();
                    regs["chr_start_bank_s"] = new List<String>();
                    regs["chr_count"] = new List<String>();
                    regs["game_save"] = new List<String>();
                    regs["game_type"] = new List<String>();
                    regs["cursor_pos"] = new List<String>();

                    int c = 0;
                    foreach (var game in games)
                    {
                        int prgSize = game.ROM.PRG.Length;
                        int chrSize = game.ROM.CHR.Length;
                        int prgPos = game.PrgPos;
                        int chrPos = Math.Max(game.ChrPos, 0);
                        int chrBase = (chrPos / 0x2000) >> 4;
                        int prgBase = (prgPos / 0x2000) >> 4;
                        int prgRoundSize = 1;
                        while (prgRoundSize < prgSize) prgRoundSize *= 2;
                        int chrRoundSize = 1;
                        while (chrRoundSize < chrSize || chrRoundSize < 0x2000) chrRoundSize *= 2;

                        MapperInfo mapperInfo;
                        if (!mappers.TryGetValue(game.ROM.Mapper, out mapperInfo))
                            throw new Exception(string.Format("Unknowm mapper #{0} for {1} ", game.ROM.Mapper, game.FileName));
                        if (chrSize > 256 * 1024)
                            throw new Exception(string.Format("CHR is too big in {0} ", game.FileName));
                        if (game.ROM.Mirroring == NesFile.MirroringType.FourScreenVram && game.ROM.CHR.Length > 256 * 1024 - 0x1000)
                            throw new Exception(string.Format("Four-screen and such big CHR is not supported for {0} ", game.FileName));

                        // Some unusual games
                        if (game.ROM.Mapper == 1) // MMC1 ?
                        {
                            switch (game.ROM.CRC32)
                            {
                                case 0xc6182024:	// Romance of the 3 Kingdoms
                                case 0x2225c20f:	// Genghis Khan
                                case 0x4642dda6:	// Nobunaga's Ambition
                                case 0x29449ba9:	// ""        "" (J)
                                case 0x2b11e0b0:	// ""        "" (J)
                                case 0xb8747abf:	// Best Play Pro Yakyuu Special (J)
                                case 0xc9556b36:	// Final Fantasy I & II (J) [!]
                                    Console.WriteLine("WARNING! {0} uses 16KB of WRAM", game.FileName);
                                    mapperInfo.MapperFlags |= 1; // flag to support 16KB of WRAM
                                    break;
                            }
                        }
                        if (game.ROM.Mapper == 4) // MMC3 ?
                        {
                            switch (game.ROM.CRC32)
                            {
                                case 0x93991433:	// Low-G-Man
                                case 0xaf65aa84:	// Low-G-Man
                                    Console.WriteLine("WARNING! WRAM will be disabled for {0}", Path.GetFileName(game.FileName));
                                    mapperInfo.WramEnabled = false; // disable WRAM
                                    break;
                            }
                        }
                        switch (game.ROM.CRC32)
                        {
                            case 0x78b657ac: // "Armadillo (J) [!].nes"
                            case 0xb3d92e78: // "Armadillo (J) [T+Eng1.01_Vice Translations].nes"
                            case 0x0fe6e6a5: // "Armadillo (J) [T+Rus1.00 Chief-Net (23.05.2012)].nes"
                            case 0xe62e3382: // "MiG 29 - Soviet Fighter (Camerica) [!].nes"
                            case 0x1bc686a8: // "Fire Hawk (Camerica) [!].nes"
                                Console.WriteLine("WARNING! {0} is not compatible with Dendy", Path.GetFileName(game.FileName));
                                game.Flags |= GameFlags.WillNotWorkOnDendy;
                                break;
                        }
                        if (string.IsNullOrEmpty(game.ToString()))
                            game.Flags = GameFlags.Separator;

                        byte @params = 0;
                        if (mapperInfo.WramEnabled) @params |= 1; // enable SRAM
                        if (chrSize == 0) @params |= 2; // enable CHR write
                        if (game.ROM.Mirroring == NesFile.MirroringType.Horizontal) @params |= 8; // default mirroring
                        if (game.ROM.Mirroring == NesFile.MirroringType.FourScreenVram)
                        {
                            @params |= 32; // four screen
                            game.Flags |= GameFlags.WillNotWorkOnNewDendy; // not external NTRAM on new famiclones :(
                        }
                        @params |= 0x80; // lockout

                        regs["reg_0"].Add(string.Format("${0:X2}", ((prgPos / 0x4000) >> 8) & 0xFF));    // PRG base
                        regs["reg_1"].Add(string.Format("${0:X2}", (prgPos / 0x4000) & 0xFF));  // PRG base
                        regs["reg_2"].Add(string.Format("${0:X2}", (0xFF ^ (prgRoundSize / 0x4000 - 1)) & 0xFF)); // PRG mask
                        regs["reg_3"].Add(string.Format("${0:X2}", (mapperInfo.PrgMode << 5) | 0)); // PRG mode
                        regs["reg_4"].Add(string.Format("${0:X2}", (mapperInfo.ChrMode << 5) | (0xFF ^ (chrRoundSize / 0x2000 - 1)) & 0x1F)); // CHR mode, CHR mask
                        regs["reg_5"].Add(string.Format("${0:X2}", (game.ROM.Battery ? 0x02 : 0x01)));    // SRAM page
                        regs["reg_6"].Add(string.Format("${0:X2}", (mapperInfo.MapperFlags << 5) | mapperInfo.MapperReg)); // Flags, mapper
                        regs["reg_7"].Add(string.Format("${0:X2}", @params));                   // Parameters
                        regs["chr_start_bank_h"].Add(string.Format("${0:X2}", ((chrPos / 0x8000) >> 7) & 0xFF));
                        regs["chr_start_bank_l"].Add(string.Format("${0:X2}", ((chrPos / 0x8000) << 1) & 0xFF));
                        regs["chr_start_bank_s"].Add(string.Format("${0:X2}", ((chrPos % 0x8000) >> 8) | 0x80));
                        regs["chr_count"].Add(string.Format("${0:X2}", chrSize / 0x2000));
                        regs["game_save"].Add(string.Format("${0:X2}", !game.ROM.Battery ? 0 : game.SaveId));
                        regs["game_type"].Add(string.Format("${0:X2}", (byte)game.Flags));
                        regs["cursor_pos"].Add(string.Format("${0:X2}", game.ToString().Length + 4 /*+ (++c).ToString().Length*/));
                    }

                    byte baseBank = 0;
                    var asmResult = new StringBuilder();
                    asmResult.AppendLine("; Games database");
                    int regCount = 0;
                    foreach (var reg in regs.Keys)
                    {
                        c = 0;
                        foreach (var r in regs[reg])
                        {
                            if (c % 256 == 0)
                            {
                                asmResult.AppendLine();
                                asmResult.AppendLine("  .bank " + (baseBank + c / 256 * 2));
                                asmResult.AppendLine(string.Format("  .org ${0:X4}", 0x8000 + regCount * 0x100));
                                asmResult.Append("loader_data_" + reg + (c == 0 ? "" : "_" + c.ToString()) + ":");
                            }
                            //asmResult.AppendLine("  .db " + string.Join(", ", regs[reg]));
                            if (c % 16 == 0)
                            {
                                asmResult.AppendLine();
                                asmResult.Append("  .db");
                            }
                            asmResult.AppendFormat(((c % 16 != 0) ? "," : "") + " {0}", r);
                            c++;
                        }
                        asmResult.AppendLine();
                        regCount++;
                    }

                    asmResult.AppendLine();
                    asmResult.AppendLine();
                    //asmResult.Append("  .dw");
                    c = 0;
                    foreach (var game in games)
                    {
                        if (c % 256 == 0)
                        {
                            asmResult.AppendLine();
                            asmResult.AppendLine("  .bank " + (baseBank + c / 256 * 2));
                            asmResult.AppendLine("  .org $9000");
                            asmResult.AppendLine("game_names_list" + (c == 0 ? "" : "_" + c.ToString()) + ":");
                            asmResult.AppendLine("  .dw game_names" + (c == 0 ? "" : "_" + c.ToString()));
                            asmResult.AppendLine("game_names" + (c == 0 ? "" : "_" + c.ToString()) + ":");
                        }
                        //asmResult.AppendFormat(((c > 0) ? "," : "") + " game_name_" + c);
                        asmResult.AppendLine("  .dw game_name_" + c);
                        c++;
                    }

                    asmResult.AppendLine();
                    asmResult.AppendLine();
                    asmResult.AppendLine("; Game names");
                    c = 0;
                    foreach (var game in games)
                    {
                        asmResult.AppendLine();
                        if (c % 256 == 0)
                        {
                            asmResult.AppendLine("  .bank " + (baseBank + c / 256 * 2 + 1));
                            if (baseBank + c / 256 * 2 + 1 >= 62) throw new Exception("Bank overflow! Too many games?");
                            asmResult.AppendLine("  .org $A000");
                        }
                        asmResult.AppendLine("; " + game.ToString());
                        asmResult.AppendLine("game_name_" + c + ":");
                        var name = StringToTiles(string.Format(/*"{0}. "+*/"{1}", c + 1, game.ToString()));
                        var asm = BytesToAsm(name);
                        asmResult.Append(asm);
                        c++;
                    }

                    asmResult.AppendLine();
                    asmResult.AppendLine("  .bank 14");
                    asmResult.AppendLine("  .org $C600");
                    asmResult.AppendLine();
                    asmResult.AppendLine("games_count:");
                    asmResult.AppendLine("  .dw " + (games.Count - hiddenCount));
                    asmResult.AppendLine();
                    asmResult.AppendLine();
                    asmResult.AppendLine("games_offset:");
                    asmResult.AppendLine("  .db " + ((games.Count - hiddenCount) > 10 ? 0 : 5 - (games.Count - hiddenCount) / 2));
                    asmResult.AppendLine();
                    asmResult.AppendLine();
                    asmResult.AppendLine("maximum_scroll:");
                    asmResult.AppendLine("  .dw " + Math.Max(0, games.Count - 11 - hiddenCount));
                    asmResult.AppendLine();
                    asmResult.AppendLine();
                    asmResult.AppendLine("build_info0:");
                    asmResult.Append(BytesToAsm(StringToTiles("FILE: " + Path.GetFileName(optionGames))));
                    asmResult.AppendLine("build_info2:");
                    asmResult.Append(BytesToAsm(StringToTiles("BUILD DATE: " + DateTime.Now.ToString("yyyy-MM-dd"))));
                    asmResult.AppendLine("build_info3:");
                    asmResult.Append(BytesToAsm(StringToTiles("BUILD TIME: " + DateTime.Now.ToString("HH:mm:ss"))));
                    asmResult.AppendLine("console_type_text:");
                    asmResult.Append(BytesToAsm(StringToTiles("CONSOLE TYPE:")));
                    asmResult.AppendLine("console_type_NTSC:");
                    asmResult.Append(BytesToAsm(StringToTiles("NTSC")));
                    asmResult.AppendLine("console_type_PAL:");
                    asmResult.Append(BytesToAsm(StringToTiles("PAL")));
                    asmResult.AppendLine("console_type_DENDY:");
                    asmResult.Append(BytesToAsm(StringToTiles("DENDY")));
                    asmResult.AppendLine("console_type_NEW:");
                    asmResult.Append(BytesToAsm(StringToTiles("NEW")));
                    asmResult.AppendLine("saving_text:");
                    if (optionLanguage == "rus")
                        asmResult.Append(BytesToAsm(StringToTiles("  СОХРАНЯЕМСЯ... НЕ ВЫКЛЮЧАЙ!   ")));
                    else
                        asmResult.Append(BytesToAsm(StringToTiles("   SAVING...  KEEP POWER ON!    ")));
                    File.WriteAllText(optionAsm, asmResult.ToString());
                    asmResult.AppendLine("incompatible_console_text:");
                    if (optionLanguage == "rus")
                        asmResult.Append(BytesToAsm(StringToTiles("     ИЗВИНИТЕ,  ДАННАЯ ИГРА       НЕСОВМЕСТИМА С ЭТОЙ КОНСОЛЬЮ                                        НАЖМИТЕ ЛЮБУЮ КНОПКУ      ")));
                    else
                        asmResult.Append(BytesToAsm(StringToTiles("    SORRY,  THIS GAME IS NOT      COMPATIBLE WITH THIS CONSOLE                                          PRESS ANY BUTTON        ")));
                    asmResult.AppendLine("sram_test_ok_text:");
                    asmResult.Append(BytesToAsm(StringToTiles("PRG RAM TEST: OK")));
                    asmResult.AppendLine("sram_test_failed_text:");
                    asmResult.Append(BytesToAsm(StringToTiles("PRG RAM TEST: FAILED")));
                    asmResult.AppendLine("chr_test_ok_text:");
                    asmResult.Append(BytesToAsm(StringToTiles("CHR RAM TEST: OK")));
                    asmResult.AppendLine("chr_test_failed_text:");
                    asmResult.Append(BytesToAsm(StringToTiles("CHR RAM TEST: FAILED")));
                    File.WriteAllText(optionAsm, asmResult.ToString());

                    XmlWriterSettings xmlSettings = new XmlWriterSettings();
                    xmlSettings.Indent = true;
                    StreamWriter str = new StreamWriter(optionOffsets);
                    XmlWriter offsetsXml = XmlWriter.Create(str, xmlSettings);
                    offsetsXml.WriteStartDocument();
                    offsetsXml.WriteStartElement("Offsets");

                    offsetsXml.WriteStartElement("Info");
                    offsetsXml.WriteElementString("Size", romSize.ToString());
                    offsetsXml.WriteElementString("RomCount", games.Count.ToString());
                    offsetsXml.WriteElementString("GamesFile", Path.GetFileName(optionGames));
                    offsetsXml.WriteEndElement();

                    offsetsXml.WriteStartElement("ROMs");
                    foreach (var game in games)
                    {
                        if (game.FileName == "-") continue;
                        offsetsXml.WriteStartElement("ROM");
                        offsetsXml.WriteElementString("FileName", game.FileName);
                        if (!game.ToString().StartsWith("?"))
                            offsetsXml.WriteElementString("MenuName", game.ToString());
                        offsetsXml.WriteElementString("PrgOffset", string.Format("{0:X8}", game.PrgPos));
                        if (game.ROM.CHR.Length > 0)
                            offsetsXml.WriteElementString("ChrOffset", string.Format("{0:X8}", game.ChrPos));
                        offsetsXml.WriteElementString("Mapper", game.ROM.Mapper.ToString());
                        if (game.SaveId > 0)
                            offsetsXml.WriteElementString("SaveId", game.SaveId.ToString());
                        offsetsXml.WriteEndElement();
                    }
                    offsetsXml.WriteEndElement();

                    offsetsXml.WriteEndElement();
                    offsetsXml.Close();
                }
                else
                {
                    using (var xmlFile = File.OpenRead(optionOffsets))
                    {
                        var offsetsXml = new XPathDocument(xmlFile);
                        XPathNavigator offsetsNavigator = offsetsXml.CreateNavigator();
                        XPathNodeIterator offsetsIterator = offsetsNavigator.Select("/Offsets/Info/Size");
                        int size = -1;
                        while (offsetsIterator.MoveNext())
                        {
                            size = offsetsIterator.Current.ValueAsInt;
                        }
                        //size = 64 * 1024 * 1024;

                        if (size < 0) throw new Exception("Invalid offsets file");
                        var result = new byte[size];

                        Console.Write("Loading loader... ");
                        var loaderFile = new NesFile(optionLoader);
                        Array.Copy(loaderFile.PRG, 0, result, 0, loaderFile.PRG.Length);
                        Console.WriteLine("OK.");

                        offsetsIterator = offsetsNavigator.Select("/Offsets/ROMs/ROM");
                        while (offsetsIterator.MoveNext())
                        {
                            var currentRom = offsetsIterator.Current;
                            string filename = null;
                            int prgOffset = -1;
                            int chrOffset = -1;
                            var descs = currentRom.SelectDescendants(XPathNodeType.Element, false);
                            while (descs.MoveNext())
                            {
                                var param = descs.Current;
                                switch (param.Name.ToLower())
                                {
                                    case "filename":
                                        filename = param.Value;
                                        break;
                                    case "prgoffset":
                                        prgOffset = int.Parse(param.Value, System.Globalization.NumberStyles.HexNumber);
                                        break;
                                    case "chroffset":
                                        chrOffset = int.Parse(param.Value, System.Globalization.NumberStyles.HexNumber);
                                        break;
                                }
                            }

                            if (!string.IsNullOrEmpty(filename))
                            {
                                Console.Write("Loading {0}... ", filename);
                                var nesFile = new NesFile(filename);
                                if (prgOffset >= 0)
                                    Array.Copy(nesFile.PRG, 0, result, prgOffset, nesFile.PRG.Length);
                                if (chrOffset >= 0)
                                    Array.Copy(nesFile.CHR, 0, result, chrOffset, nesFile.CHR.Length);
                                Console.WriteLine("OK.");
                            }
                        }

                        if (!string.IsNullOrEmpty(optionUnif))
                        {
                            var u = new UnifFile();
                            u.Mapper = "COOLGIRL";
                            u.Fields["MIRR"] = new byte[] { 5 };
                            u.Fields["PRG0"] = result;
                            u.Fields["BATR"] = new byte[] { 1 };
                            u.Version = 5;
                            u.Save(optionUnif);

                            uint sizeFixed = 1;
                            while (sizeFixed < result.Length) sizeFixed <<= 1;
                            var resultSizeFixed = new byte[sizeFixed];
                            Array.Copy(result, 0, resultSizeFixed, 0, result.Length);
                            for (int i = result.Length; i < sizeFixed; i++)
                                resultSizeFixed[i] = 0xFF;
                            var md5 = System.Security.Cryptography.MD5.Create();
                            var md5hash = md5.ComputeHash(resultSizeFixed);
                            Console.Write("ROM MD5: ");
                            foreach (var b in md5hash)
                                Console.Write("{0:x2}", b);
                            Console.WriteLine();
                        }
                        if (!string.IsNullOrEmpty(optionBin))
                        {
                            File.WriteAllBytes(optionBin, result);
                        }
                    }
                }
                Console.WriteLine("Done.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message + ex.StackTrace);
                return 2;
            }
            return 0;
        }
Esempio n. 3
0
        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");
            }
        }
Esempio n. 4
0
 public Game(string fileName, string menuName = null, Dictionary <uint, GameFix> fixes = null)
 {
     // Separators
     if (fileName == "-")
     {
         MenuName = (string.IsNullOrWhiteSpace(menuName) || menuName == "-") ? "" : menuName;
         FileName = "";
         Flags   |= GameFlags.Separator;
     }
     else
     {
         Console.WriteLine("Loading {0}...", Path.GetFileName(fileName));
         FileName = fileName;
         if (string.IsNullOrWhiteSpace(menuName))
         {
             // Menu name based on filename
             MenuName = Regex.Replace(Path.GetFileNameWithoutExtension(fileName), @"( ?\[.*?\])|( \(.\))", string.Empty).Replace("_", " ").ToUpper().Replace(", THE", "").Trim();
         }
         else
         {
             MenuName = menuName.Trim();
             if (MenuName == "?")
             {
                 Flags |= GameFlags.Hidden;
             }
         }
         // Strip long names
         if (MenuName.Length > 28)
         {
             MenuName = MenuName.Substring(0, 25).Trim() + "...";
         }
         uint crc;
         try
         {
             var nesFile   = new NesFile(fileName);
             var fixResult = nesFile.CorrectRom();
             if (fixResult != 0)
             {
                 Console.WriteLine(" Invalid header. Fix: " + fixResult);
             }
             PRG           = nesFile.PRG;
             PrgSize       = (uint)nesFile.PRG.Count();
             CHR           = nesFile.CHR;
             ChrSize       = (uint)nesFile.CHR.Count();
             Battery       = nesFile.Battery;
             Mapper        = $"{nesFile.Mapper:D3}" + ((nesFile.Submapper > 0) ? $":{nesFile.Submapper}" : "");
             Mirroring     = nesFile.Mirroring;
             ContainerType = NesContainerType.iNES;
             if (nesFile.Trainer != null && nesFile.Trainer.Count() > 0)
             {
                 throw new NotImplementedException(string.Format("{0} - trained games are not supported yet", Path.GetFileName(fileName)));
             }
             if (nesFile.Version == NesFile.iNesVersion.NES20)
             {
                 PrgRamSize = nesFile.PrgRamSize + nesFile.PrgNvRamSize;
                 ChrRamSize = nesFile.ChrRamSize + nesFile.ChrNvRamSize;
             }
             crc = nesFile.CalculateCRC32();
         }
         catch (InvalidDataException)
         {
             var unifFile = new UnifFile(fileName);
             PRG     = unifFile.Fields.Where(k => k.Key.StartsWith("PRG")).OrderBy(k => k.Key).SelectMany(i => i.Value);
             PrgSize = (uint)PRG.Count();
             CHR     = unifFile.Fields.Where(k => k.Key.StartsWith("CHR")).OrderBy(k => k.Key).SelectMany(i => i.Value);
             ChrSize = (uint)CHR.Count();
             Battery = unifFile.Battery;
             var mapper = unifFile.Mapper;
             if (mapper.StartsWith("NES-") || mapper.StartsWith("UNL-") || mapper.StartsWith("HVC-") || mapper.StartsWith("BTL-") || mapper.StartsWith("BMC-"))
             {
                 mapper = mapper.Substring(4);
             }
             Mapper        = mapper;
             Mirroring     = unifFile.Mirroring;
             ContainerType = NesContainerType.UNIF;
             crc           = unifFile.CalculateCRC32();
         }
         // Check for fixes database
         if (fixes != null)
         {
             GameFix fix = null;
             if (fixes.TryGetValue(crc, out fix))
             {
                 if (fix.PrgRamSize.HasValue)
                 {
                     PrgRamSize = fix.PrgRamSize * 1024;
                 }
                 if (fix.ChrRamSize.HasValue)
                 {
                     ChrRamSize = fix.ChrRamSize * 1024;
                 }
                 if (fix.Battery.HasValue)
                 {
                     Battery = fix.Battery.Value;
                 }
                 if (fix.WillNotWorkOnPal)
                 {
                     Flags |= GameFlags.WillNotWorkOnPal;
                 }
                 if (fix.WillNotWorkOnNtsc)
                 {
                     Flags |= GameFlags.WillNotWorkOnNtsc;
                 }
                 if (fix.WillNotWorkOnDendy)
                 {
                     Flags |= GameFlags.WillNotWorkOnDendy;
                 }
                 if (fix.WillNotWorkOnNewFamiclone)
                 {
                     Flags |= GameFlags.WillNotWorkOnNewFamiclone;
                 }
             }
         }
         // External NTRAM is not supported on new famiclones
         if (Mirroring == NesFile.MirroringType.FourScreenVram)
         {
             Flags |= GameFlags.WillNotWorkOnNewFamiclone;
         }
         // Check for round sizes
         if (PrgSize > 0)
         {
             uint roundSize = 1;
             while (roundSize < PrgSize)
             {
                 roundSize <<= 1;
             }
             if (roundSize > PrgSize)
             {
                 var newPrg = new byte[roundSize];
                 for (uint i = PrgSize; i < roundSize; i++)
                 {
                     newPrg[i] = 0xFF;
                 }
                 Array.Copy(PRG.ToArray(), newPrg, PrgSize);
                 PRG     = newPrg;
                 PrgSize = roundSize;
             }
         }
         if (ChrSize > 0)
         {
             uint roundSize = 1;
             while (roundSize < ChrSize)
             {
                 roundSize <<= 1;
             }
             if (roundSize > ChrSize)
             {
                 var newChr = new byte[roundSize];
                 for (uint i = ChrSize; i < roundSize; i++)
                 {
                     newChr[i] = 0xFF;
                 }
                 Array.Copy(CHR.ToArray(), newChr, ChrSize);
                 CHR     = newChr;
                 ChrSize = roundSize;
             }
         }
     }
 }
Esempio n. 5
0
        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");
        }