diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/BoardSystem.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/BoardSystem.cs
index 9bce79c363..3ca902eb77 100644
--- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/BoardSystem.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/BoardSystem.cs
@@ -408,13 +408,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
///
public class CartInfo
{
- public NESGameInfo game;
public GameInfo DB_GameInfo;
+ public string name;
- public short chr_size;
- public short prg_size;
- public short wram_size, vram_size;
- public byte pad_h, pad_v, mapper;
+ public int chr_size;
+ public int prg_size;
+ public int wram_size, vram_size;
+ public byte pad_h, pad_v;
public bool wram_battery;
public bool bad;
/// in [0,3]; combination of bits 0 and 3 of flags6. try not to use; will be null for bootgod-identified roms always
@@ -429,19 +429,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public override string ToString()
{
- return string.Format("map={0},pr={1},ch={2},wr={3},vr={4},ba={5},pa={6}|{7},brd={8},sys={9}", mapper, prg_size, chr_size, wram_size, vram_size, wram_battery ? 1 : 0, pad_h, pad_v, board_type, system);
+ return string.Format("pr={1},ch={2},wr={3},vr={4},ba={5},pa={6}|{7},brd={8},sys={9}", board_type, prg_size, chr_size, wram_size, vram_size, wram_battery ? 1 : 0, pad_h, pad_v, board_type, system);
}
}
- ///
- /// Logical game information. May exist in form of several carts (different revisions)
- ///
- public class NESGameInfo
- {
- public string name;
- public List carts = new List();
- }
-
///
/// finds a board class which can handle the provided cart
///
@@ -503,15 +494,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
var gi = Database.CheckDatabase(hash);
if (gi == null) return null;
- NESGameInfo game = new NESGameInfo();
CartInfo cart = new CartInfo();
- game.carts.Add(cart);
//try generating a bootgod cart descriptor from the game database
var dict = gi.GetOptionsDict();
- game.name = gi.Name;
cart.DB_GameInfo = gi;
- cart.game = game;
if (!dict.ContainsKey("board"))
throw new Exception("NES gamedb entries must have a board identifier!");
cart.board_type = dict["board"];
@@ -585,8 +572,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
//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()));
- NESGameInfo currGame = null;
CartInfo currCart = null;
+ string currName = null;
while (xmlreader.Read())
{
switch (state)
@@ -594,8 +581,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
case 0:
if (xmlreader.NodeType == XmlNodeType.Element && xmlreader.Name == "game")
{
- currGame = new NESGameInfo();
- currGame.name = xmlreader.GetAttribute("name");
+ currName = xmlreader.GetAttribute("name");
state = 1;
}
break;
@@ -606,7 +592,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
currCart.pcb = xmlreader.GetAttribute("pcb");
int mapper = int.Parse(xmlreader.GetAttribute("mapper"));
if (validate && mapper > 255) throw new Exception("didnt expect mapper>255!");
- currCart.mapper = (byte)mapper;
+ // we don't actually use this value at all; only the board name
state = 3;
}
break;
@@ -646,7 +632,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
case 4:
if (xmlreader.NodeType == XmlNodeType.EndElement && xmlreader.Name == "cartridge")
{
- currGame.carts.Add(currCart);
+ sha1_table[currCart.sha1].Add(currCart);
currCart = null;
state = 5;
}
@@ -656,33 +642,23 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (xmlreader.NodeType == XmlNodeType.Element && xmlreader.Name == "cartridge")
{
currCart = new CartInfo();
- currCart.game = currGame;
currCart.system = xmlreader.GetAttribute("system");
currCart.sha1 = "sha1:" + xmlreader.GetAttribute("sha1");
+ currCart.name = currName;
state = 2;
}
if (xmlreader.NodeType == XmlNodeType.EndElement && xmlreader.Name == "game")
{
- games.Add(currGame);
- currGame = null;
+ currName = null;
state = 0;
}
break;
}
} //end xmlreader loop
- //analyze
- foreach (NESGameInfo game in games)
- {
- foreach (CartInfo cart in game.carts)
- {
- sha1_table[cart.sha1].Add(cart);
- }
- }
+
}
-
- List games = new List(); //maybe we dont need to track this
Bag sha1_table = new Bag();
public List Identify(string sha1)
diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs
index 89110ea40b..b770cf8a23 100644
--- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs
@@ -432,6 +432,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
Type boardType = null;
CartInfo choice = null;
CartInfo iNesHeaderInfo = null;
+ CartInfo iNesHeaderInfoV2 = null;
List hash_sha1_several = new List();
string hash_sha1 = null, hash_md5 = null;
Unif unif = null;
@@ -444,6 +445,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("UNIF")))
{
LoadWriteLine("Found UNIF header:");
+ LoadWriteLine(unif.GetCartInfo());
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;
@@ -480,41 +482,45 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
}
else
{
- fixed (byte* bfile = &file[0])
+ 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:" + 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:");
+ LoadWriteLine(iNesHeaderInfo.ToString());
+ if (iNesHeaderInfoV2 != null)
{
- var header = (iNES_HEADER*)bfile;
- if (!header->CheckID()) throw new InvalidOperationException("iNES header not found");
- header->Cleanup();
+ LoadWriteLine("Found iNES V2 header:");
+ LoadWriteLine(iNesHeaderInfoV2);
+ }
+ LoadWriteLine("Since this is iNES we can (somewhat) confidently parse PRG/CHR banks to hash.");
- //now that we know we have an iNES header, we can try to ignore it.
+ LoadWriteLine("headerless rom hash: {0}", hash_sha1);
+ LoadWriteLine("headerless rom hash: {0}", hash_md5);
- 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)
- {
- //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 (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:" + 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);
}
}
@@ -570,46 +576,49 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
{
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);
- if (iNES_board == null)
- throw new Exception("couldnt identify NES rom");
- choice.board_type = iNES_board;
-
- //try spinning up a board with 8K wram and with 0K wram to see if one answers
- try
+ // try to spin up V2 header first, then V1 header
+ if (iNesHeaderInfoV2 != null)
{
- boardType = FindBoard(choice, origin, InitialMapperRegisterValues);
- }
- catch { }
- if (boardType == null)
- {
- if (choice.wram_size == 8) choice.wram_size = 0;
- else if (choice.wram_size == 0) choice.wram_size = 8;
try
{
- boardType = FindBoard(choice, origin, InitialMapperRegisterValues);
+ boardType = FindBoard(iNesHeaderInfoV2, origin, InitialMapperRegisterValues);
}
catch { }
- if (boardType != null)
- LoadWriteLine("Ambiguous iNES wram size resolved as {0}k", choice.wram_size);
+ 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)
+ {
+ try
+ {
+ boardType = FindBoard(iNesHeaderInfo, origin, InitialMapperRegisterValues);
+ }
+ catch {}
+ if (boardType == null)
+ LoadWriteLine("Failed to load as iNES V1");
+ else
+ choice = iNesHeaderInfo;
+
+ // 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: " + iNES_board);
- choice.game.name = gameInfo.Name;
+ LoadWriteLine("Chose board from iNES heuristics:");
+ LoadWriteLine(choice);
origin = EDetectionOrigin.INES;
}
}
- //TODO - generate better name with region and system
- game_name = choice.game.name;
//find a INESBoard to handle this
boardType = FindBoard(choice, origin, InitialMapperRegisterValues);
diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
index aed69be847..8092a11fd1 100644
--- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/iNES.cs
@@ -1,117 +1,103 @@
+using System;
using System.IO;
using BizHawk.Common;
+using System.Linq;
+using System.Text;
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
partial class NES
{
- ///
- /// attempts to classify a rom based on iNES header information.
- /// this used to be way more complex. but later, we changed to have a board class implement a "MAPPERXXX" virtual board type and all hacks will be in there
- /// so theres nothing to do here but pick the board type corresponding to the cart
- ///
- static class iNESBoardDetector
+ private static int iNES2Wram(int i)
{
- public static string Detect(CartInfo cartInfo)
- {
- return string.Format("MAPPER{0:d3}",cartInfo.mapper);
- }
+ if (i == 0) return 0;
+ if (i == 15) throw new InvalidDataException();
+ return 1 << (i + 6);
}
- unsafe struct iNES_HEADER
+ public static bool DetectFromINES(byte[] data, out CartInfo Cart, out CartInfo CartV2)
{
- public fixed byte ID[4]; /*NES^Z*/
- public byte ROM_size;
- public byte VROM_size;
- public byte ROM_type;
- public byte ROM_type2;
- public byte wram_size;
- public byte flags9, flags10;
- public byte zero11, zero12, zero13, zero14, zero15;
-
-
- public bool CheckID()
+ byte[] ID = new byte[4];
+ Buffer.BlockCopy(data, 0, ID, 0, 4);
+ if (!ID.SequenceEqual(Encoding.ASCII.GetBytes("NES\x1A")))
{
- fixed (iNES_HEADER* self = &this)
- return 0 == Util.Memcmp(self, "NES\x1A", 4);
+ Cart = null;
+ CartV2 = null;
+ return false;
}
- //some cleanup code recommended by fceux
- public void Cleanup()
+ if ((data[7] & 0x0c) == 0x08)
{
- fixed (iNES_HEADER* self = &this)
+ // process as iNES v2
+ CartV2 = new CartInfo();
+
+ CartV2.prg_size = data[4] | data[9] << 8 & 0xf00;
+ CartV2.chr_size = data[5] | data[9] << 4 & 0xf00;
+ CartV2.prg_size *= 16;
+ CartV2.chr_size *= 8;
+
+ 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 = string.Format("MAPPER{0:d4}-{1:d2}", mapper, submapper);
+
+ 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)
{
- if (0 == Util.Memcmp((byte*)(self) + 0x7, "DiskDude", 8))
- {
- Util.Memset((byte*)(self) + 0x7, 0, 0x9);
- }
-
- if (0 == Util.Memcmp((byte*)(self) + 0x7, "demiforce", 9))
- {
- Util.Memset((byte*)(self) + 0x7, 0, 0x9);
- }
-
- if (0 == Util.Memcmp((byte*)(self) + 0x8, "blargg", 6)) //found a test rom with this in there, mucking up the wram size
- {
- Util.Memset((byte*)(self) + 0x8, 0, 6);
- }
-
- if (0 == Util.Memcmp((byte*)(self) + 0xA, "Ni03", 4))
- {
- if (0 == Util.Memcmp((byte*)(self) + 0x7, "Dis", 3))
- Util.Memset((byte*)(self) + 0x7, 0, 0x9);
- else
- Util.Memset((byte*)(self) + 0xA, 0, 0x6);
- }
+ case 0: CartV2.pad_v = 1; break;
+ case 1: CartV2.pad_h = 1; break;
}
+
+ }
+ else
+ {
+ CartV2 = null;
}
- public CartInfo Analyze(TextWriter report)
+ // process as iNES v1
+ // the DiskDude cleaning is no longer; get better roms
+ Cart = new CartInfo();
+
+ Cart.prg_size = data[4];
+ Cart.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
+
{
- var ret = new CartInfo();
- ret.game = new NESGameInfo();
- int mapper = (ROM_type >> 4);
- mapper |= (ROM_type2 & 0xF0);
- ret.mapper = (byte)mapper;
- int mirroring = (ROM_type & 1);
- if ((ROM_type & 8) != 0) mirroring += 2;
- if (mirroring == 0) ret.pad_v = 1;
- else if (mirroring == 1) ret.pad_h = 1;
- ret.inesmirroring = mirroring;
- ret.prg_size = (short)(ROM_size * 16);
- if (ret.prg_size == 0)
- ret.prg_size = 256 * 16;
- ret.chr_size = (short)(VROM_size * 8);
- ret.wram_battery = (ROM_type & 2) != 0;
- if (ROM_type.Bit(2))
- report.WriteLine("DANGER: According to the flags, this iNES has a trainer in it! We don't support this garbage.");
-
- if(wram_size != 0 || flags9 != 0 || flags10 != 0 || zero11 != 0 || zero12 != 0 || zero13 != 0 || zero14 != 0 || zero15 != 0)
- {
- report.WriteLine("Looks like you have an iNES 2.0 header, or some other kind of weird garbage.");
- report.WriteLine("We haven't bothered to support iNES 2.0.");
- report.WriteLine("We might, if we can find anyone who uses it. Let us know.");
- }
-
- ret.wram_size = (short)(wram_size * 8);
- //0 is supposed to mean 8KB (for compatibility, as this is an extension to original iNES format)
- if (ret.wram_size == 0)
- {
- report.WriteLine("iNES wr=0 interpreted as wr=8");
- ret.wram_size = 8;
- }
-
- //iNES wants us to assume that no chr -> 8KB vram
- if (ret.chr_size == 0) ret.vram_size = 8;
-
- //let's not put a lot of hacks in here. that's what the databases are for.
- //for example of one not to add: videomation hack to change vram = 8 -> 16
-
- string mirror_memo = mirroring == 0 ? "horz" : (mirroring == 1 ? "vert" : "4screen");
- report.WriteLine("map={0},pr={1},ch={2},wr={3},vr={4},ba={5},mir={6}({7})", ret.mapper, ret.prg_size, ret.chr_size, ret.wram_size, ret.vram_size, ret.wram_battery ? 1 : 0, mirroring, mirror_memo);
-
- return ret;
+ int mapper = data[6] >> 4 | data[7] & 0xf0;
+ Cart.board_type = string.Format("MAPPER{0:d3}", mapper);
}
+
+ 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))
+ Console.WriteLine("DANGER: According to the flags, this iNES has a trainer in it! We don't support this garbage.");
+
+ return true;
}
}