From 31c7edf8dd1ef93ec0f05701d4219c417a110d32 Mon Sep 17 00:00:00 2001 From: zeromus Date: Tue, 16 Oct 2012 22:27:48 +0000 Subject: [PATCH] nes-unif support. we will need to explicitly add every UNIF board we support to their respective mappers, because [1] each unif board name carries with it its own unique assumptions about which chips are present. [2] the unif board names may not be matching bootgod's, which we accept as canonical; also fix a small memory leak due to nes boards not being disposed during scan process. --- .../Consoles/Nintendo/NES/BoardSystem.cs | 21 +- .../Nintendo/NES/Boards/MMC3_family/MMC3.cs | 2 +- .../Nintendo/NES/Boards/MMC3_family/TxROM.cs | 6 + .../Consoles/Nintendo/NES/Boards/Mapper091.cs | 2 +- .../NES/Boards/Namcot1xx/Namcot1xx.cs | 3 +- .../Consoles/Nintendo/NES/Core.cs | 1 + .../Consoles/Nintendo/NES/NES.cs | 287 ++++++++++-------- .../Consoles/Nintendo/NES/Unif.cs | 12 +- BizHawk.Emulation/Database/Database.cs | 6 +- 9 files changed, 203 insertions(+), 137 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs index b2ea4efd95..6128e451f2 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/BoardSystem.cs @@ -262,12 +262,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo protected void AssertVram(params int[] vram) { Assert_memtype(Cart.vram_size, "vram", vram); } protected void Assert_memtype(int value, string name, int[] valid) { + if (DisableConfigAsserts) return; foreach (int i in valid) if (value == i) return; Assert(false, "unhandled {0} size of {1}", name,value); } protected void AssertBattery(bool has_bat) { Assert(Cart.wram_battery == has_bat); } public virtual void ApplyCustomAudio(short[] samples) { } + + public bool DisableConfigAsserts = false; } //this will be used to track classes that implement boards @@ -340,10 +343,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo nes.cart = cart; foreach (var type in INESBoardImplementors) { - INESBoard board = (INESBoard)Activator.CreateInstance(type); - board.Create(nes); - if (board.Configure(origin)) - return type; + using (NESBoardBase board = (NESBoardBase)Activator.CreateInstance(type)) + { + //unif demands that the boards set themselves up with expected legal values based on the board size + //except, i guess, for the rom/chr sizes. go figure. + //so, disable the asserts here + if (origin == EDetectionOrigin.UNIF) + board.DisableConfigAsserts = true; + + board.Create(nes); + if (board.Configure(origin)) + { + return type; + } + } } return null; } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3.cs index 25e36221cc..4082beb5e5 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/MMC3.cs @@ -301,7 +301,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void Dispose() { - mmc3.Dispose(); + if(mmc3 != null) mmc3.Dispose(); } public override void SyncState(Serializer ser) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TxROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TxROM.cs index 649bcc58c5..e3391e7623 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TxROM.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/MMC3_family/TxROM.cs @@ -85,6 +85,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo AssertPrg(128, 256, 512); AssertChr(128, 256); AssertVram(0); AssertWram(8); AssertBattery(false); break; + case "UNIF_TSROM": + Cart.wram_size = 8; + Cart.wram_battery = false; + break; case "ACCLAIM-MC-ACC": //alien 3 (U) AssertPrg(128); AssertChr(128); AssertVram(0); AssertWram(0); AssertBattery(false); @@ -93,6 +97,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo AssertPrg(128); AssertChr(128); AssertVram(0); AssertWram(0); AssertBattery(false); break; + + default: return false; } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Mapper091.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Mapper091.cs index 87f8b7e577..be959df580 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Mapper091.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Mapper091.cs @@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo { prg_regs_8k.Dispose(); chr_regs_2k.Dispose(); - mmc3.Dispose(); + if(mmc3 != null) mmc3.Dispose(); base.Dispose(); } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namcot1xx.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namcot1xx.cs index d5184d0267..588dfb63b2 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namcot1xx.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/Namcot1xx/Namcot1xx.cs @@ -107,7 +107,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo public override void Dispose() { - mapper.Dispose(); + if(mapper != null) + mapper.Dispose(); } public override void SyncState(Serializer ser) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs index 4452bd2f22..e017afa1eb 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Core.cs @@ -21,6 +21,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo string game_name; //friendly name exposed to user and used as filename base CartInfo cart; //the current cart prototype. should be moved into the board, perhaps INESBoard board; //the board hardware that is currently driving things + EDetectionOrigin origin = EDetectionOrigin.None; public bool SoundOn = true; int sprdma_countdown; bool _irq_apu; //various irq signals that get merged to the cpu irq pin diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index ba7cc4f52f..8049b8ef8a 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -409,7 +409,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo public enum EDetectionOrigin { - None, BootGodDB, GameDB, INES + None, BootGodDB, GameDB, INES, UNIF } StringWriter LoadReport; @@ -440,66 +440,91 @@ namespace BizHawk.Emulation.Consoles.Nintendo LoadWriteLine("------"); LoadWriteLine("BEGIN NES rom analysis:"); byte[] file = rom; + + Type boardType = null; + CartInfo choice = null; + CartInfo iNesHeaderInfo = null; + List hash_sha1_several = new List(); + string hash_sha1 = null, hash_md5 = null; + Unif unif = null; + + origin = EDetectionOrigin.None; + if (file.Length < 16) throw new Exception("Alleged NES rom too small to be anything useful"); if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("UNIF"))) - throw new Exception("You've tried to open a UNIF rom. We don't have any UNIF roms to test with. Please consult the developers."); - fixed (byte* bfile = &file[0]) { - var origin = EDetectionOrigin.None; - - var header = (iNES_HEADER*)bfile; - if (!header->CheckID()) throw new InvalidOperationException("iNES header not found"); - header->Cleanup(); - - //now that we know we have an iNES header, we can try to ignore it. - - List hash_sha1_several = new List(); - string hash_sha1 = "sha1:" + Util.Hash_SHA1(file,16,file.Length - 16); + LoadWriteLine("Found UNIF header:"); + LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash."); + unif = new Unif(new MemoryStream(file)); + hash_sha1 = unif.GetCartInfo().sha1; hash_sha1_several.Add(hash_sha1); - string hash_md5 = "md5:" + Util.Hash_MD5(file, 16, file.Length - 16); - - LoadWriteLine("Found iNES header:"); - CartInfo iNesHeaderInfo = header->Analyze(new MyWriter(LoadReport)); - LoadWriteLine("Since this is iNES we can (somewhat) confidently parse PRG/CHR banks to hash."); - - LoadWriteLine("headerless rom hash: {0}", hash_sha1); - LoadWriteLine("headerless rom hash: {0}", hash_md5); - - if (iNesHeaderInfo.prg_size == 16) + } + else + { + fixed (byte* bfile = &file[0]) { - //8KB prg can't be stored in iNES format, which counts 16KB prg banks. - //so a correct hash will include only 8KB. - LoadWriteLine("Since this rom has a 16 KB PRG, we'll hash it as 8KB too for bootgod's DB:"); - var msTemp = new MemoryStream(); - msTemp.Write(file, 16, 8 * 1024); //add prg - msTemp.Write(file, 16 + 16 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr - msTemp.Flush(); - var bytes = msTemp.ToArray(); - var hash = "sha1:" + Util.Hash_SHA1(bytes, 0, bytes.Length); - LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); - hash_sha1_several.Add(hash); - hash = "md5:" + Util.Hash_MD5(bytes, 0, bytes.Length); - LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); - } + var header = (iNES_HEADER*)bfile; + if (!header->CheckID()) throw new InvalidOperationException("iNES header not found"); + header->Cleanup(); - Type boardType = null; - CartInfo choice = null; - if (USE_DATABASE) - choice = IdentifyFromBootGodDB(hash_sha1_several); - if (choice == null) - { - LoadWriteLine("Could not locate game in nescartdb"); - if (USE_DATABASE) + //now that we know we have an iNES header, we can try to ignore it. + + hash_sha1 = "sha1:" + Util.Hash_SHA1(file, 16, file.Length - 16); + hash_sha1_several.Add(hash_sha1); + hash_md5 = "md5:" + Util.Hash_MD5(file, 16, file.Length - 16); + + LoadWriteLine("Found iNES header:"); + iNesHeaderInfo = header->Analyze(new MyWriter(LoadReport)); + LoadWriteLine("Since this is iNES we can (somewhat) confidently parse PRG/CHR banks to hash."); + + LoadWriteLine("headerless rom hash: {0}", hash_sha1); + LoadWriteLine("headerless rom hash: {0}", hash_md5); + + if (iNesHeaderInfo.prg_size == 16) { - choice = IdentifyFromGameDB(hash_md5); - if (choice == null) - { - choice = IdentifyFromGameDB(hash_sha1); - } + //8KB prg can't be stored in iNES format, which counts 16KB prg banks. + //so a correct hash will include only 8KB. + LoadWriteLine("Since this rom has a 16 KB PRG, we'll hash it as 8KB too for bootgod's DB:"); + var msTemp = new MemoryStream(); + msTemp.Write(file, 16, 8 * 1024); //add prg + msTemp.Write(file, 16 + 16 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr + msTemp.Flush(); + var bytes = msTemp.ToArray(); + var hash = "sha1:" + Util.Hash_SHA1(bytes, 0, bytes.Length); + LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); + hash_sha1_several.Add(hash); + hash = "md5:" + Util.Hash_MD5(bytes, 0, bytes.Length); + LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); } + } + } + + if (USE_DATABASE) + choice = IdentifyFromBootGodDB(hash_sha1_several); + if (choice == null) + { + LoadWriteLine("Could not locate game in nescartdb"); + if (USE_DATABASE) + { + if(hash_md5 != null) choice = IdentifyFromGameDB(hash_md5); if (choice == null) { - LoadWriteLine("Could not locate game in bizhawk gamedb"); + choice = IdentifyFromGameDB(hash_sha1); + } + } + if (choice == null) + { + LoadWriteLine("Could not locate game in bizhawk gamedb"); + if (unif != null) + { + LoadWriteLine("Using information from UNIF header"); + choice = unif.GetCartInfo(); + choice.game = new NESGameInfo(); + choice.game.name = gameInfo.Name; + origin = EDetectionOrigin.UNIF; + } + if (iNesHeaderInfo != null) + { LoadWriteLine("Attempting inference from iNES header"); choice = iNesHeaderInfo; string iNES_board = iNESBoardDetector.Detect(choice); @@ -530,80 +555,89 @@ namespace BizHawk.Emulation.Consoles.Nintendo choice.game.name = gameInfo.Name; origin = EDetectionOrigin.INES; } - else - { - origin = EDetectionOrigin.GameDB; - LoadWriteLine("Chose board from bizhawk gamedb: " + choice.board_type); - //gamedb entries that dont specify prg/chr sizes can infer it from the ines header - if (choice.prg_size == -1) choice.prg_size = iNesHeaderInfo.prg_size; - if (choice.chr_size == -1) choice.chr_size = iNesHeaderInfo.chr_size; - if (choice.vram_size == -1) choice.vram_size = iNesHeaderInfo.vram_size; - if (choice.wram_size == -1) choice.wram_size = iNesHeaderInfo.wram_size; - } } else { - LoadWriteLine("Chose board from nescartdb:"); - LoadWriteLine(choice); - origin = EDetectionOrigin.BootGodDB; + origin = EDetectionOrigin.GameDB; + LoadWriteLine("Chose board from bizhawk gamedb: " + choice.board_type); + //gamedb entries that dont specify prg/chr sizes can infer it from the ines header + if (choice.prg_size == -1) choice.prg_size = iNesHeaderInfo.prg_size; + if (choice.chr_size == -1) choice.chr_size = iNesHeaderInfo.chr_size; + if (choice.vram_size == -1) choice.vram_size = iNesHeaderInfo.vram_size; + if (choice.wram_size == -1) choice.wram_size = iNesHeaderInfo.wram_size; } - - //TODO - generate better name with region and system - game_name = choice.game.name; - - //find a INESBoard to handle this - boardType = FindBoard(choice, origin); - if (boardType == null) - throw new Exception("No class implements the necessary board type: " + choice.board_type); - - if (choice.DB_GameInfo != null) - choice.bad = choice.DB_GameInfo.IsRomStatusBad(); - - LoadWriteLine("Final game detection results:"); + } + else + { + LoadWriteLine("Chose board from nescartdb:"); LoadWriteLine(choice); - LoadWriteLine("\"" + game_name + "\""); - LoadWriteLine("Implemented by: class " + boardType.Name); + origin = EDetectionOrigin.BootGodDB; + } + + //TODO - generate better name with region and system + game_name = choice.game.name; + + //find a INESBoard to handle this + boardType = FindBoard(choice, origin); + if (boardType == null) + throw new Exception("No class implements the necessary board type: " + choice.board_type); + + if (choice.DB_GameInfo != null) + choice.bad = choice.DB_GameInfo.IsRomStatusBad(); + + LoadWriteLine("Final game detection results:"); + LoadWriteLine(choice); + LoadWriteLine("\"" + game_name + "\""); + LoadWriteLine("Implemented by: class " + boardType.Name); + if (choice.bad) + { + LoadWriteLine("~~ ONE WAY OR ANOTHER, THIS DUMP IS KNOWN TO BE *BAD* ~~"); + LoadWriteLine("~~ YOU SHOULD FIND A BETTER FILE ~~"); + } + + LoadWriteLine("END NES rom analysis"); + LoadWriteLine("------"); + + board = (INESBoard)Activator.CreateInstance(boardType); + + cart = choice; + board.Create(this); + board.Configure(origin); + + if (origin == EDetectionOrigin.BootGodDB) + { + RomStatus = RomStatus.GoodDump; + CoreOutputComm.RomStatusAnnotation = "Identified from BootGod's database"; + } + if (origin == EDetectionOrigin.UNIF) + { + RomStatus = RomStatus.NotInDatabase; + CoreOutputComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious"; + } + if (origin == EDetectionOrigin.INES) + { + RomStatus = RomStatus.NotInDatabase; + CoreOutputComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong"; + } + if (origin == EDetectionOrigin.GameDB) + { if (choice.bad) { - LoadWriteLine("~~ ONE WAY OR ANOTHER, THIS DUMP IS KNOWN TO BE *BAD* ~~"); - LoadWriteLine("~~ YOU SHOULD FIND A BETTER FILE ~~"); + RomStatus = RomStatus.BadDump; } - - LoadWriteLine("END NES rom analysis"); - LoadWriteLine("------"); - - board = (INESBoard)Activator.CreateInstance(boardType); - - cart = choice; - board.Create(this); - board.Configure(origin); - - if (origin == EDetectionOrigin.BootGodDB) + else { - RomStatus = RomStatus.GoodDump; - CoreOutputComm.RomStatusAnnotation = "Identified from BootGod's database"; - } - if (origin == EDetectionOrigin.INES) - { - RomStatus = RomStatus.NotInDatabase; - CoreOutputComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong"; - } - if (origin == EDetectionOrigin.GameDB) - { - if (choice.bad) - { - RomStatus = RomStatus.BadDump; - } - else - { - RomStatus = choice.DB_GameInfo.Status; - } + RomStatus = choice.DB_GameInfo.Status; } + } - LoadReport.Flush(); - CoreOutputComm.RomStatusDetails = LoadReport.ToString(); + LoadReport.Flush(); + CoreOutputComm.RomStatusDetails = LoadReport.ToString(); - //create the board's rom and vrom + //create the board's rom and vrom + if (iNesHeaderInfo != null) + { + //pluck the necessary bytes out of the file board.ROM = new byte[choice.prg_size * 1024]; Array.Copy(file, 16, board.ROM, 0, board.ROM.Length); if (choice.chr_size > 0) @@ -612,18 +646,23 @@ namespace BizHawk.Emulation.Consoles.Nintendo int vrom_offset = iNesHeaderInfo.prg_size * 1024; Array.Copy(file, 16 + vrom_offset, board.VROM, 0, board.VROM.Length); } - - //create the vram and wram if necessary - if (cart.wram_size != 0) - board.WRAM = new byte[cart.wram_size * 1024]; - if (cart.vram_size != 0) - board.VRAM = new byte[cart.vram_size * 1024]; - - board.PostConfigure(); - - HardReset(); - SetupMemoryDomains(); } + else + { + board.ROM = unif.GetPRG(); + board.VROM = unif.GetCHR(); + } + + //create the vram and wram if necessary + if (cart.wram_size != 0) + board.WRAM = new byte[cart.wram_size * 1024]; + if (cart.vram_size != 0) + board.VRAM = new byte[cart.vram_size * 1024]; + + board.PostConfigure(); + + HardReset(); + SetupMemoryDomains(); } void SyncState(Serializer ser) diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Unif.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Unif.cs index 6507d1f63d..b8c3ac0b25 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Unif.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Unif.cs @@ -67,18 +67,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo switch (tmp[0]) { case 0: // hmirror - ci.pad_h = 1; - ci.pad_v = 0; - break; - case 1: // vmirror ci.pad_h = 0; ci.pad_v = 1; break; + case 1: // vmirror + ci.pad_h = 1; + ci.pad_v = 0; + break; } } if (chunks.TryGetValue("MAPR", out tmp)) ci.board_type = Encoding.ASCII.GetString(tmp); + ci.board_type = ci.board_type.TrimEnd('\0'); + ci.board_type = "UNIF_" + ci.board_type; // is there any way using System.Security.Cryptography.SHA1 to compute the hash of // prg concatentated with chr? i couldn't figure it out, so this implementation is dumb @@ -88,7 +90,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo ms.Write(chrrom, 0, chrrom.Length); ms.Close(); byte[] all = ms.ToArray(); - ci.sha1 = Util.Hash_SHA1(all, 0, all.Length); + ci.sha1 = "sha1:" + Util.Hash_SHA1(all, 0, all.Length); } } diff --git a/BizHawk.Emulation/Database/Database.cs b/BizHawk.Emulation/Database/Database.cs index 7b1136fbab..e736ee8e0c 100644 --- a/BizHawk.Emulation/Database/Database.cs +++ b/BizHawk.Emulation/Database/Database.cs @@ -128,6 +128,11 @@ namespace BizHawk switch (ext) { + case ".NES": + case ".UNF": + case ".FDS": + Game.System = "NES"; + break; case ".SFC": case ".SMC": Game.System = "SNES"; @@ -144,7 +149,6 @@ namespace BizHawk case ".GEN": case ".MD": case ".SMD": Game.System = "GEN"; break; - case ".NES": Game.System = "NES"; break; case ".A26": Game.System = "A26"; break; case ".COL": Game.System = "COLV"; break; case ".ROM":