/// <summary> /// finds a board class which can handle the provided cart /// </summary> static Type FindBoard(CartInfo cart, EDetectionOrigin origin, Dictionary <string, string> properties) { NES nes = new NES(); nes.cart = cart; Type ret = null; lock (INESBoardImplementors) foreach (var type in INESBoardImplementors) { 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); board.InitialRegisterValues = properties; if (board.Configure(origin)) { #if DEBUG if (ret != null) { throw new Exception(string.Format("Boards {0} and {1} both responded to Configure!", ret, type)); } else { ret = type; } #else return(type); #endif } } } return(ret); }
/// <summary> /// looks up from the game DB /// </summary> private CartInfo IdentifyFromGameDB(string hash) { var gi = Database.CheckDatabase(hash); if (gi == null) { return(null); } CartInfo cart = new CartInfo(); //try generating a bootgod cart descriptor from the game database var dict = gi.GetOptions(); cart.GameInfo = gi; if (!dict.TryGetValue("board", out var board)) { throw new Exception("NES gamedb entries must have a board identifier!"); } cart.BoardType = board; if (dict.TryGetValue("system", out var system)) { cart.System = system; } cart.PrgSize = dict.TryGetValue("PRG", out var prgSizeStr) ? short.Parse(prgSizeStr) : -1; cart.ChrSize = dict.TryGetValue("CHR", out var chrSizeStr) ? short.Parse(chrSizeStr) : -1; cart.VramSize = dict.TryGetValue("VRAM", out var vramSizeStr) ? short.Parse(vramSizeStr) : -1; cart.WramSize = dict.TryGetValue("WRAM", out var wramSizeStr) ? short.Parse(wramSizeStr) : -1; if (dict.TryGetValue("PAD_H", out var padHStr)) { cart.PadH = byte.Parse(padHStr); } if (dict.TryGetValue("PAD_V", out var padVStr)) { cart.PadV = byte.Parse(padVStr); } if (dict.TryGetValue("MIR", out var mirStr)) { if (mirStr == "H") { cart.PadV = 1; cart.PadH = 0; } else if (mirStr == "V") { cart.PadH = 1; cart.PadV = 0; } } if (dict.ContainsKey("BAD")) { cart.Bad = true; } if (dict.TryGetValue("MMC3", out var mmc3)) { cart.Chips.Add(mmc3); } if (dict.TryGetValue("PCB", out var pcb)) { cart.Pcb = pcb; } if (dict.TryGetValue("BATT", out var batteryStr)) { cart.WramBattery = bool.Parse(batteryStr); } if (dict.TryGetValue("palette", out var palette)) { cart.Palette = palette; } if (dict.TryGetValue("vs_security", out var vsSecurityStr)) { cart.VsSecurity = byte.Parse(vsSecurityStr); } return(cart); }
public BootGodDb(string basePath) { // notes: there can be multiple each of prg,chr,wram,vram // we aren't tracking the individual hashes yet. string xmlPath = Path.Combine(basePath, "NesCarts.xml"); string x7zPath = Path.Combine(basePath, "NesCarts.7z"); bool loadXml = File.Exists(xmlPath); using var nesCartFile = new HawkFile(loadXml ? xmlPath : x7zPath); if (!loadXml) { nesCartFile.BindFirst(); } var stream = nesCartFile.GetStream(); // in anticipation of any slowness annoying people, and just for shits and giggles, i made a super fast parser int state = 0; var xmlReader = XmlReader.Create(stream); CartInfo currCart = null; string currName = null; while (xmlReader.Read()) { switch (state) { case 0: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "game") { currName = xmlReader.GetAttribute("name"); state = 1; } break; case 2: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "board") { currCart.BoardType = xmlReader.GetAttribute("type"); currCart.Pcb = xmlReader.GetAttribute("pcb"); int mapper = int.Parse(xmlReader.GetAttribute("mapper")); if (validate && mapper > 255) { throw new Exception("didnt expect mapper>255!"); } // we don't actually use this value at all; only the board name state = 3; } break; case 3: if (xmlReader.NodeType == XmlNodeType.Element) { switch (xmlReader.Name) { case "prg": currCart.PrgSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "chr": currCart.ChrSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "vram": currCart.VramSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "wram": currCart.WramSize += (short)ParseSize(xmlReader.GetAttribute("size")); if (xmlReader.GetAttribute("battery") != null) { currCart.WramBattery = true; } break; case "pad": currCart.PadH = byte.Parse(xmlReader.GetAttribute("h")); currCart.PadV = byte.Parse(xmlReader.GetAttribute("v")); break; case "chip": currCart.Chips.Add(xmlReader.GetAttribute("type")); if (xmlReader.GetAttribute("battery") != null) { currCart.WramBattery = true; } break; } } else if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "board") { state = 4; } break; case 4: if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "cartridge") { _sha1Table[currCart.Sha1].Add(currCart); currCart = null; state = 5; } break; case 5: case 1: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "cartridge") { currCart = new CartInfo(); currCart.System = xmlReader.GetAttribute("system"); currCart.Sha1 = "sha1:" + xmlReader.GetAttribute("sha1"); currCart.Name = currName; state = 2; } if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "game") { currName = null; state = 0; } break; } } //end xmlreader loop }
/// <summary> /// looks up from the game DB /// </summary> private CartInfo IdentifyFromGameDB(string hash) { var gi = Database.CheckDatabase(hash); if (gi == null) { return(null); } CartInfo cart = new CartInfo(); //try generating a bootgod cart descriptor from the game database var dict = gi.GetOptions(); cart.GameInfo = gi; if (!dict.ContainsKey("board")) { throw new Exception("NES gamedb entries must have a board identifier!"); } cart.BoardType = dict["board"]; if (dict.ContainsKey("system")) { cart.System = dict["system"]; } cart.PrgSize = -1; cart.VramSize = -1; cart.WramSize = -1; cart.ChrSize = -1; if (dict.ContainsKey("PRG")) { cart.PrgSize = short.Parse(dict["PRG"]); } if (dict.ContainsKey("CHR")) { cart.ChrSize = short.Parse(dict["CHR"]); } if (dict.ContainsKey("VRAM")) { cart.VramSize = short.Parse(dict["VRAM"]); } if (dict.ContainsKey("WRAM")) { cart.WramSize = short.Parse(dict["WRAM"]); } if (dict.ContainsKey("PAD_H")) { cart.PadH = byte.Parse(dict["PAD_H"]); } if (dict.ContainsKey("PAD_V")) { cart.PadV = byte.Parse(dict["PAD_V"]); } if (dict.ContainsKey("MIR")) { if (dict["MIR"] == "H") { cart.PadV = 1; cart.PadH = 0; } else if (dict["MIR"] == "V") { cart.PadH = 1; cart.PadV = 0; } } if (dict.ContainsKey("BAD")) { cart.Bad = true; } if (dict.ContainsKey("MMC3")) { cart.Chips.Add(dict["MMC3"]); } if (dict.ContainsKey("PCB")) { cart.Pcb = dict["PCB"]; } if (dict.ContainsKey("BATT")) { cart.WramBattery = bool.Parse(dict["BATT"]); } if (dict.ContainsKey("palette")) { cart.Palette = dict["palette"]; } if (dict.ContainsKey("vs_security")) { cart.VsSecurity = byte.Parse(dict["vs_security"]); } return(cart); }
public BootGodDB() { //notes: there can be multiple each of prg,chr,wram,vram //we arent tracking the individual hashes yet. //in anticipation of any slowness annoying people, and just for shits and giggles, i made a super fast parser int state = 0; var xmlreader = XmlReader.Create(new MemoryStream(_GetDatabaseBytes())); CartInfo currCart = null; string currName = null; while (xmlreader.Read()) { switch (state) { case 0: if (xmlreader.NodeType == XmlNodeType.Element && xmlreader.Name == "game") { currName = xmlreader.GetAttribute("name"); state = 1; } break; case 2: if (xmlreader.NodeType == XmlNodeType.Element && xmlreader.Name == "board") { currCart.board_type = xmlreader.GetAttribute("type"); currCart.pcb = xmlreader.GetAttribute("pcb"); int mapper = int.Parse(xmlreader.GetAttribute("mapper")); if (validate && mapper > 255) { throw new Exception("didnt expect mapper>255!"); } // we don't actually use this value at all; only the board name state = 3; } break; case 3: if (xmlreader.NodeType == XmlNodeType.Element) { switch (xmlreader.Name) { case "prg": currCart.prg_size += (short)ParseSize(xmlreader.GetAttribute("size")); break; case "chr": currCart.chr_size += (short)ParseSize(xmlreader.GetAttribute("size")); break; case "vram": currCart.vram_size += (short)ParseSize(xmlreader.GetAttribute("size")); break; case "wram": currCart.wram_size += (short)ParseSize(xmlreader.GetAttribute("size")); if (xmlreader.GetAttribute("battery") != null) { currCart.wram_battery = true; } break; case "pad": currCart.pad_h = byte.Parse(xmlreader.GetAttribute("h")); currCart.pad_v = byte.Parse(xmlreader.GetAttribute("v")); break; case "chip": currCart.chips.Add(xmlreader.GetAttribute("type")); break; } } else if (xmlreader.NodeType == XmlNodeType.EndElement && xmlreader.Name == "board") { state = 4; } break; case 4: if (xmlreader.NodeType == XmlNodeType.EndElement && xmlreader.Name == "cartridge") { sha1_table[currCart.sha1].Add(currCart); currCart = null; state = 5; } break; case 5: case 1: if (xmlreader.NodeType == XmlNodeType.Element && xmlreader.Name == "cartridge") { currCart = new CartInfo(); currCart.system = xmlreader.GetAttribute("system"); currCart.sha1 = "sha1:" + xmlreader.GetAttribute("sha1"); currCart.name = currName; state = 2; } if (xmlreader.NodeType == XmlNodeType.EndElement && xmlreader.Name == "game") { currName = null; state = 0; } break; } } //end xmlreader loop }
/// <summary> /// looks up from the game DB /// </summary> CartInfo IdentifyFromGameDB(string hash) { var gi = Database.CheckDatabase(hash); if (gi == null) { return(null); } CartInfo cart = new CartInfo(); //try generating a bootgod cart descriptor from the game database var dict = gi.GetOptionsDict(); cart.DB_GameInfo = gi; if (!dict.ContainsKey("board")) { throw new Exception("NES gamedb entries must have a board identifier!"); } cart.board_type = dict["board"]; if (dict.ContainsKey("system")) { cart.system = dict["system"]; } cart.prg_size = -1; cart.vram_size = -1; cart.wram_size = -1; cart.chr_size = -1; if (dict.ContainsKey("PRG")) { cart.prg_size = short.Parse(dict["PRG"]); } if (dict.ContainsKey("CHR")) { cart.chr_size = short.Parse(dict["CHR"]); } if (dict.ContainsKey("VRAM")) { cart.vram_size = short.Parse(dict["VRAM"]); } if (dict.ContainsKey("WRAM")) { cart.wram_size = short.Parse(dict["WRAM"]); } if (dict.ContainsKey("PAD_H")) { cart.pad_h = byte.Parse(dict["PAD_H"]); } if (dict.ContainsKey("PAD_V")) { cart.pad_v = byte.Parse(dict["PAD_V"]); } if (dict.ContainsKey("MIR")) { if (dict["MIR"] == "H") { cart.pad_v = 1; cart.pad_h = 0; } else if (dict["MIR"] == "V") { cart.pad_h = 1; cart.pad_v = 0; } } if (dict.ContainsKey("BAD")) { cart.bad = true; } if (dict.ContainsKey("MMC3")) { cart.chips.Add(dict["MMC3"]); } if (dict.ContainsKey("PCB")) { cart.pcb = dict["PCB"]; } if (dict.ContainsKey("BATT")) { cart.wram_battery = bool.Parse(dict["BATT"]); } if (dict.ContainsKey("palette")) { cart.palette = dict["palette"]; } if (dict.ContainsKey("vs_security")) { cart.vs_security = byte.Parse(dict["vs_security"]); } return(cart); }
public void Init(GameInfo gameInfo, byte[] rom, byte[] fdsbios = null) { LoadReport = new StringWriter(); LoadWriteLine("------"); LoadWriteLine("BEGIN NES rom analysis:"); byte[] file = rom; Type boardType = null; CartInfo choice = null; CartInfo iNesHeaderInfo = null; CartInfo iNesHeaderInfoV2 = null; List <string> hash_sha1_several = new List <string>(); string hash_sha1 = null, hash_md5 = null; Unif unif = null; Dictionary <string, string> InitialMapperRegisterValues = new Dictionary <string, string>(SyncSettings.BoardProperties); 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"))) { unif = new Unif(new MemoryStream(file)); LoadWriteLine("Found UNIF header:"); LoadWriteLine(unif.CartInfo); LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash."); hash_sha1 = unif.CartInfo.sha1; hash_sha1_several.Add(hash_sha1); LoadWriteLine("headerless rom hash: {0}", hash_sha1); } else if (file.Take(5).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("NESM\x1A"))) { origin = EDetectionOrigin.NSF; LoadWriteLine("Loading as NSF"); var nsf = new NSFFormat(); nsf.WrapByteArray(file); cart = new CartInfo(); var nsfboard = new NSFBoard(); nsfboard.Create(this); nsfboard.ROM = rom; nsfboard.InitNSF(nsf); nsfboard.InitialRegisterValues = InitialMapperRegisterValues; nsfboard.Configure(origin); nsfboard.WRAM = new byte[cart.wram_size * 1024]; Board = nsfboard; Board.PostConfigure(); AutoMapperProps.Populate(Board, SyncSettings); Console.WriteLine("Using NTSC display type for NSF for now"); _display_type = Common.DisplayType.NTSC; HardReset(); return; } else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A")) || file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI"))) { // danger! this is a different codepath with an early return. accordingly, some // code is duplicated twice... // FDS roms are just fed to the board, we don't do much else with them origin = EDetectionOrigin.FDS; LoadWriteLine("Found FDS header."); if (fdsbios == null) { throw new MissingFirmwareException("Missing FDS Bios"); } cart = new CartInfo(); var fdsboard = new FDS(); fdsboard.biosrom = fdsbios; fdsboard.SetDiskImage(rom); fdsboard.Create(this); // at the moment, FDS doesn't use the IRVs, but it could at some point in the future fdsboard.InitialRegisterValues = InitialMapperRegisterValues; fdsboard.Configure(origin); Board = fdsboard; //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(); AutoMapperProps.Populate(Board, SyncSettings); Console.WriteLine("Using NTSC display type for FDS disk image"); _display_type = Common.DisplayType.NTSC; HardReset(); return; } else { byte[] nesheader = new byte[16]; Buffer.BlockCopy(file, 0, nesheader, 0, 16); bool exists = true; if (!DetectFromINES(nesheader, out iNesHeaderInfo, out iNesHeaderInfoV2)) { // we don't have an ines header, check if the game hash is in the game db exists = false; Console.WriteLine("headerless ROM, using Game DB"); hash_md5 = "md5:" + file.HashMD5(0, file.Length); hash_sha1 = "sha1:" + file.HashSHA1(0, file.Length); if (hash_md5 != null) { choice = IdentifyFromGameDB(hash_md5); } if (choice == null) { choice = IdentifyFromGameDB(hash_sha1); } if (choice == null) { hash_sha1_several.Add(hash_sha1); choice = IdentifyFromBootGodDB(hash_sha1_several); if (choice == null) { LoadWriteLine("Could not locate game in nescartdb"); } else { LoadWriteLine("Chose board from nescartdb:"); LoadWriteLine(choice); origin = EDetectionOrigin.BootGodDB; } } if (choice == null) { throw new InvalidOperationException("iNES header not found and no gamedb entry"); } } if (exists) { //now that we know we have an iNES header, we can try to ignore it. hash_sha1 = "sha1:" + file.HashSHA1(16, file.Length - 16); hash_sha1_several.Add(hash_sha1); hash_md5 = "md5:" + file.HashMD5(16, file.Length - 16); LoadWriteLine("Found iNES header:"); LoadWriteLine(iNesHeaderInfo.ToString()); if (iNesHeaderInfoV2 != null) { LoadWriteLine("Found iNES V2 header:"); LoadWriteLine(iNesHeaderInfoV2); } 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) { //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 if (file.Length >= (16 * 1024 + iNesHeaderInfo.chr_size * 1024 + 16)) { // This assumes that even though the PRG is only 8k the CHR is still written // 16k into the file, which is not always the case (e.x. Galaxian RevA) msTemp.Write(file, 16 + 16 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr } else if (file.Length >= (8 * 1024 + iNesHeaderInfo.chr_size * 1024 + 16)) { // maybe the PRG is only 8k msTemp.Write(file, 16 + 8 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr } else { // we failed somehow // most likely the header is wrong Console.WriteLine("WARNING: 16kb PRG iNES header but unable to parse"); } msTemp.Flush(); var bytes = msTemp.ToArray(); var hash = "sha1:" + bytes.HashSHA1(0, bytes.Length); LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); hash_sha1_several.Add(hash); hash = "md5:" + bytes.HashMD5(0, bytes.Length); LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); } } } if (USE_DATABASE) { if (hash_md5 != null) { choice = IdentifyFromGameDB(hash_md5); } if (choice == null) { choice = IdentifyFromGameDB(hash_sha1); } if (choice == null) { LoadWriteLine("Could not locate game in bizhawk gamedb"); } 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 (iNesHeaderInfo != null) { 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 if (unif != null) { if (choice.prg_size == -1) { choice.prg_size = unif.CartInfo.prg_size; } if (choice.chr_size == -1) { choice.chr_size = unif.CartInfo.chr_size; } // unif has no wram\vram sizes; hope the board impl can figure it out... if (choice.vram_size == -1) { choice.vram_size = 0; } if (choice.wram_size == -1) { choice.wram_size = 0; } } } //if this is still null, we have to try it some other way. nescartdb perhaps? if (choice == null) { choice = IdentifyFromBootGodDB(hash_sha1_several); if (choice == null) { LoadWriteLine("Could not locate game in nescartdb"); } else { LoadWriteLine("Chose board from nescartdb:"); LoadWriteLine(choice); origin = EDetectionOrigin.BootGodDB; } } } //if choice is still null, try UNIF and iNES if (choice == null) { if (unif != null) { LoadWriteLine("Using information from UNIF header"); choice = unif.CartInfo; //ok, i have this Q-Boy rom with no VROM and no VRAM. //we also certainly have games with VROM and no VRAM. //looks like FCEUX policy is to allocate 8KB of chr ram no matter what UNLESS certain flags are set. but what's the justification for this? please leave a note if you go debugging in it again. //well, we know we can't have much of a NES game if there's no VROM unless there's VRAM instead. //so if the VRAM isn't set, choose 8 for it. //TODO - unif loading code may need to use VROR flag to transform chr_size=8 to vram_size=8 (need example) if (choice.chr_size == 0 && choice.vram_size == 0) { choice.vram_size = 8; } //(do we need to suppress this in case theres a CHR rom? probably not. nes board base will use ram if no rom is available) origin = EDetectionOrigin.UNIF; } if (iNesHeaderInfo != null) { LoadWriteLine("Attempting inference from iNES header"); // try to spin up V2 header first, then V1 header if (iNesHeaderInfoV2 != null) { try { boardType = FindBoard(iNesHeaderInfoV2, origin, InitialMapperRegisterValues); } catch { } if (boardType == null) { LoadWriteLine("Failed to load as iNES V2"); } else { choice = iNesHeaderInfoV2; } // V2 might fail but V1 might succeed because we don't have most V2 aliases setup; and there's // no reason to do so except when needed } if (boardType == null) { choice = iNesHeaderInfo; // we're out of options, really boardType = FindBoard(iNesHeaderInfo, origin, InitialMapperRegisterValues); if (boardType == null) { LoadWriteLine("Failed to load as iNES V1"); } // do not further meddle in wram sizes. a board that is being loaded from a "MAPPERxxx" // entry should know and handle the situation better for the individual board } LoadWriteLine("Chose board from iNES heuristics:"); LoadWriteLine(choice); origin = EDetectionOrigin.INES; } } game_name = choice.name; //find a INESBoard to handle this if (choice != null) { boardType = FindBoard(choice, origin, InitialMapperRegisterValues); } else { throw new Exception("Unable to detect ROM"); } 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 = CreateBoardInstance(boardType); cart = choice; Board.Create(this); Board.InitialRegisterValues = InitialMapperRegisterValues; Board.Configure(origin); if (origin == EDetectionOrigin.BootGodDB) { RomStatus = RomStatus.GoodDump; CoreComm.RomStatusAnnotation = "Identified from BootGod's database"; } if (origin == EDetectionOrigin.UNIF) { RomStatus = RomStatus.NotInDatabase; CoreComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious"; } if (origin == EDetectionOrigin.INES) { RomStatus = RomStatus.NotInDatabase; CoreComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong"; } if (origin == EDetectionOrigin.GameDB) { if (choice.bad) { RomStatus = RomStatus.BadDump; } else { RomStatus = choice.DB_GameInfo.Status; } } byte[] trainer = null; //create the board's rom and vrom if (iNesHeaderInfo != null) { var ms = new MemoryStream(file, false); ms.Seek(16, SeekOrigin.Begin); // ines header //pluck the necessary bytes out of the file if (iNesHeaderInfo.trainer_size != 0) { trainer = new byte[512]; ms.Read(trainer, 0, 512); } Board.ROM = new byte[choice.prg_size * 1024]; ms.Read(Board.ROM, 0, Board.ROM.Length); if (choice.chr_size > 0) { Board.VROM = new byte[choice.chr_size * 1024]; int vrom_copy_size = ms.Read(Board.VROM, 0, Board.VROM.Length); if (vrom_copy_size < Board.VROM.Length) { LoadWriteLine("Less than the expected VROM was found in the file: {0} < {1}", vrom_copy_size, Board.VROM.Length); } } if (choice.prg_size != iNesHeaderInfo.prg_size || choice.chr_size != iNesHeaderInfo.chr_size) { LoadWriteLine("Warning: Detected choice has different filesizes than the INES header!"); } } else if (unif != null) { Board.ROM = unif.PRG; Board.VROM = unif.CHR; } else { // we should only get here for boards with no header var ms = new MemoryStream(file, false); ms.Seek(0, SeekOrigin.Begin); Board.ROM = new byte[choice.prg_size * 1024]; ms.Read(Board.ROM, 0, Board.ROM.Length); if (choice.chr_size > 0) { Board.VROM = new byte[choice.chr_size * 1024]; int vrom_copy_size = ms.Read(Board.VROM, 0, Board.VROM.Length); if (vrom_copy_size < Board.VROM.Length) { LoadWriteLine("Less than the expected VROM was found in the file: {0} < {1}", vrom_copy_size, Board.VROM.Length); } } } LoadReport.Flush(); CoreComm.RomStatusDetails = LoadReport.ToString(); // IF YOU DO ANYTHING AT ALL BELOW THIS LINE, MAKE SURE THE APPROPRIATE CHANGE IS MADE TO FDS (if applicable) //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(); AutoMapperProps.Populate(Board, SyncSettings); // set up display type NESSyncSettings.Region fromrom = DetectRegion(cart.system); NESSyncSettings.Region fromsettings = SyncSettings.RegionOverride; if (fromsettings != NESSyncSettings.Region.Default) { Console.WriteLine("Using system region override"); fromrom = fromsettings; } switch (fromrom) { case NESSyncSettings.Region.Dendy: _display_type = Common.DisplayType.Dendy; break; case NESSyncSettings.Region.NTSC: _display_type = Common.DisplayType.NTSC; break; case NESSyncSettings.Region.PAL: _display_type = Common.DisplayType.PAL; break; default: _display_type = Common.DisplayType.NTSC; break; } Console.WriteLine("Using NES system region of {0}", _display_type); HardReset(); if (trainer != null) { Console.WriteLine("Applying trainer"); for (int i = 0; i < 512; i++) { WriteMemory((ushort)(0x7000 + i), trainer[i]); } } }
public static bool DetectFromINES(byte[] data, out CartInfo Cart, out CartInfo CartV2) { byte[] ID = new byte[4]; Buffer.BlockCopy(data, 0, ID, 0, 4); if (!ID.SequenceEqual(Encoding.ASCII.GetBytes("NES\x1A"))) { Cart = null; CartV2 = null; return(false); } if ((data[7] & 0x0c) == 0x08) { // process as iNES v2 CartV2 = new CartInfo { prg_size = data[4] | data[9] << 8 & 0xf00, chr_size = data[5] | data[9] << 4 & 0xf00 }; CartV2.prg_size *= 16; CartV2.chr_size *= 8; CartV2.wram_battery = (data[6] & 2) != 0; // should this be respected in v2 mode?? int wrambat = iNES2Wram(data[10] >> 4); int wramnon = iNES2Wram(data[10] & 15); CartV2.wram_battery |= wrambat > 0; // fixme - doesn't handle sizes not divisible by 1024 CartV2.wram_size = (short)((wrambat + wramnon) / 1024); int mapper = data[6] >> 4 | data[7] & 0xf0 | data[8] << 8 & 0xf00; int submapper = data[8] >> 4; CartV2.board_type = $"MAPPER{mapper:d4}-{submapper:d2}"; int vrambat = iNES2Wram(data[11] >> 4); int vramnon = iNES2Wram(data[11] & 15); // hopefully a game with battery backed vram understands what to do internally CartV2.wram_battery |= vrambat > 0; CartV2.vram_size = (vrambat + vramnon) / 1024; CartV2.inesmirroring = data[6] & 1 | data[6] >> 2 & 2; switch (CartV2.inesmirroring) { case 0: CartV2.pad_v = 1; break; case 1: CartV2.pad_h = 1; break; } switch (data[12] & 1) { case 0: CartV2.system = "NES-NTSC"; break; case 1: CartV2.system = "NES-PAL"; break; } if ((data[6] & 4) != 0) { CartV2.trainer_size = 512; } } else { CartV2 = null; } // process as iNES v1 // the DiskDude cleaning is no longer; get better roms Cart = new CartInfo { prg_size = data[4], chr_size = data[5] }; if (Cart.prg_size == 0) { Cart.prg_size = 256; } Cart.prg_size *= 16; Cart.chr_size *= 8; Cart.wram_battery = (data[6] & 2) != 0; Cart.wram_size = 8; // should be data[8], but that never worked { int mapper = data[6] >> 4 | data[7] & 0xf0; Cart.board_type = $"MAPPER{mapper:d3}"; } Cart.vram_size = Cart.chr_size > 0 ? 0 : 8; Cart.inesmirroring = data[6] & 1 | data[6] >> 2 & 2; switch (Cart.inesmirroring) { case 0: Cart.pad_v = 1; break; case 1: Cart.pad_h = 1; break; } if (data[6].Bit(2)) { Cart.trainer_size = 512; } return(true); }
public void Init(GameInfo gameInfo, byte[] rom, byte[] fdsbios = null) { LoadReport = new StringWriter(); LoadWriteLine("------"); LoadWriteLine("BEGIN NES rom analysis:"); byte[] file = rom; Type boardType = null; CartInfo choice = null; CartInfo iNesHeaderInfo = null; CartInfo iNesHeaderInfoV2 = null; List <string> hash_sha1_several = new List <string>(); string hash_sha1 = null, hash_md5 = null; Unif unif = null; Dictionary <string, string> InitialMapperRegisterValues = new Dictionary <string, string>(SyncSettings.BoardProperties); 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"))) { unif = new Unif(new MemoryStream(file)); LoadWriteLine("Found UNIF header:"); LoadWriteLine(unif.CartInfo); LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash."); hash_sha1 = unif.CartInfo.sha1; hash_sha1_several.Add(hash_sha1); LoadWriteLine("headerless rom hash: {0}", hash_sha1); } else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A")) || file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI"))) { // danger! this is a different codepath with an early return. accordingly, some // code is duplicated twice... // FDS roms are just fed to the board, we don't do much else with them origin = EDetectionOrigin.FDS; LoadWriteLine("Found FDS header."); if (fdsbios == null) { throw new MissingFirmwareException("Missing FDS Bios"); } cart = new CartInfo(); var fdsboard = new FDS(); fdsboard.biosrom = fdsbios; fdsboard.SetDiskImage(rom); fdsboard.Create(this); // at the moment, FDS doesn't use the IRVs, but it could at some point in the future fdsboard.InitialRegisterValues = InitialMapperRegisterValues; fdsboard.Configure(origin); board = fdsboard; //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(); Console.WriteLine("Using NTSC display type for FDS disk image"); _display_type = Common.DisplayType.NTSC; HardReset(); return; } else { byte[] nesheader = new byte[16]; Buffer.BlockCopy(file, 0, nesheader, 0, 16); if (!DetectFromINES(nesheader, out iNesHeaderInfo, out iNesHeaderInfoV2)) { throw new InvalidOperationException("iNES header not found"); } //now that we know we have an iNES header, we can try to ignore it. hash_sha1 = "sha1:" + file.HashSHA1(16, file.Length - 16); hash_sha1_several.Add(hash_sha1); hash_md5 = "md5:" + file.HashMD5(16, file.Length - 16); LoadWriteLine("Found iNES header:"); LoadWriteLine(iNesHeaderInfo.ToString()); if (iNesHeaderInfoV2 != null) { LoadWriteLine("Found iNES V2 header:"); LoadWriteLine(iNesHeaderInfoV2); } 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) { //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:" + bytes.HashSHA1(0, bytes.Length); LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); hash_sha1_several.Add(hash); hash = "md5:" + bytes.HashMD5(0, bytes.Length); LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash); } } if (USE_DATABASE) { if (hash_md5 != null) { choice = IdentifyFromGameDB(hash_md5); } if (choice == null) { choice = IdentifyFromGameDB(hash_sha1); } if (choice == null) { LoadWriteLine("Could not locate game in bizhawk gamedb"); } 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 (iNesHeaderInfo != null) { 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 if (unif != null) { if (choice.prg_size == -1) { choice.prg_size = unif.CartInfo.prg_size; } if (choice.chr_size == -1) { choice.chr_size = unif.CartInfo.chr_size; } // unif has no wram\vram sizes; hope the board impl can figure it out... if (choice.vram_size == -1) { choice.vram_size = 0; } if (choice.wram_size == -1) { choice.wram_size = 0; } } } //if this is still null, we have to try it some other way. nescartdb perhaps? if (choice == null) { choice = IdentifyFromBootGodDB(hash_sha1_several); if (choice == null) { LoadWriteLine("Could not locate game in nescartdb"); } else { LoadWriteLine("Chose board from nescartdb:"); LoadWriteLine(choice); origin = EDetectionOrigin.BootGodDB; } } } //if choice is still null, try UNIF and iNES if (choice == null) { if (unif != null) { LoadWriteLine("Using information from UNIF header"); choice = unif.CartInfo; origin = EDetectionOrigin.UNIF; } if (iNesHeaderInfo != null) { LoadWriteLine("Attempting inference from iNES header"); // try to spin up V2 header first, then V1 header if (iNesHeaderInfoV2 != null) { try { boardType = FindBoard(iNesHeaderInfoV2, origin, InitialMapperRegisterValues); } catch { } if (boardType == null) { LoadWriteLine("Failed to load as iNES V2"); } else { choice = iNesHeaderInfoV2; } // V2 might fail but V1 might succeed because we don't have most V2 aliases setup; and there's // no reason to do so except when needed } if (boardType == null) { choice = iNesHeaderInfo; // we're out of options, really boardType = FindBoard(iNesHeaderInfo, origin, InitialMapperRegisterValues); if (boardType == null) { LoadWriteLine("Failed to load as iNES V1"); } // do not further meddle in wram sizes. a board that is being loaded from a "MAPPERxxx" // entry should know and handle the situation better for the individual board } LoadWriteLine("Chose board from iNES heuristics:"); LoadWriteLine(choice); origin = EDetectionOrigin.INES; } } game_name = choice.name; //find a INESBoard to handle this if (choice != null) { boardType = FindBoard(choice, origin, InitialMapperRegisterValues); } else { throw new Exception("Unable to detect ROM"); } 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 = CreateBoardInstance(boardType); cart = choice; board.Create(this); board.InitialRegisterValues = InitialMapperRegisterValues; board.Configure(origin); if (origin == EDetectionOrigin.BootGodDB) { RomStatus = RomStatus.GoodDump; CoreComm.RomStatusAnnotation = "Identified from BootGod's database"; } if (origin == EDetectionOrigin.UNIF) { RomStatus = RomStatus.NotInDatabase; CoreComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious"; } if (origin == EDetectionOrigin.INES) { RomStatus = RomStatus.NotInDatabase; CoreComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong"; } if (origin == EDetectionOrigin.GameDB) { if (choice.bad) { RomStatus = RomStatus.BadDump; } else { RomStatus = choice.DB_GameInfo.Status; } } LoadReport.Flush(); CoreComm.RomStatusDetails = LoadReport.ToString(); //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) { board.VROM = new byte[choice.chr_size * 1024]; int vrom_offset = iNesHeaderInfo.prg_size * 1024; // if file isn't long enough for VROM, truncate Array.Copy(file, 16 + vrom_offset, board.VROM, 0, Math.Min(board.VROM.Length, file.Length - 16 - vrom_offset)); } } else { board.ROM = unif.PRG; board.VROM = unif.CHR; } // IF YOU DO ANYTHING AT ALL BELOW THIS LINE, MAKE SURE THE APPROPRIATE CHANGE IS MADE TO FDS (if applicable) //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(); // set up display type NESSyncSettings.Region fromrom = DetectRegion(cart.system); NESSyncSettings.Region fromsettings = SyncSettings.RegionOverride; if (fromsettings != NESSyncSettings.Region.Default) { Console.WriteLine("Using system region override"); fromrom = fromsettings; } switch (fromrom) { case NESSyncSettings.Region.Dendy: _display_type = Common.DisplayType.DENDY; break; case NESSyncSettings.Region.NTSC: _display_type = Common.DisplayType.NTSC; break; case NESSyncSettings.Region.PAL: _display_type = Common.DisplayType.PAL; break; default: _display_type = Common.DisplayType.NTSC; break; } Console.WriteLine("Using NES system region of {0}", _display_type); HardReset(); }