BizHawk/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs

888 lines
26 KiB
C#
Raw Normal View History

2011-02-20 02:49:37 +00:00
using System;
using System.Linq;
2011-02-20 02:49:37 +00:00
using System.IO;
using System.Collections.Generic;
2014-07-03 19:20:34 +00:00
using BizHawk.Common.BufferExtensions;
using BizHawk.Emulation.Common;
2011-03-13 08:13:32 +00:00
2014-07-03 19:20:34 +00:00
//TODO - redo all timekeeping in terms of master clock
2013-11-14 13:15:41 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.NES
2011-02-20 02:49:37 +00:00
{
[Core(
"NesHawk",
"zeromus, natt, alyosha, adelikat",
isPorted: false,
isReleased: true)]
public partial class NES : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable,
2017-10-26 13:58:24 +00:00
IBoardInfo, ISettable<NES.NESSettings, NES.NESSyncSettings>, ICodeDataLogger
2011-02-20 02:49:37 +00:00
{
[CoreConstructor("NES")]
public NES(CoreComm comm, GameInfo game, byte[] rom, object settings, object syncSettings)
{
var ser = new BasicServiceProvider(this);
ServiceProvider = ser;
byte[] fdsbios = comm.CoreFileProvider.GetFirmware("NES", "Bios_FDS", false);
if (fdsbios != null && fdsbios.Length == 40976)
{
comm.ShowMessage("Your FDS BIOS is a bad dump. BizHawk will attempt to use it, but no guarantees! You should find a new one.");
var tmp = new byte[8192];
Buffer.BlockCopy(fdsbios, 16 + 8192 * 3, tmp, 0, 8192);
fdsbios = tmp;
}
SyncSettings = (NESSyncSettings)syncSettings ?? new NESSyncSettings();
ControllerSettings = SyncSettings.Controls;
CoreComm = comm;
MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
BootGodDB.Initialize();
2011-03-21 01:49:20 +00:00
videoProvider = new MyVideoProvider(this);
Init(game, rom, fdsbios);
if (Board is FDS)
{
DriveLightEnabled = true;
(Board as FDS).SetDriveLightCallback((val) => DriveLightOn = val);
// bit of a hack: we don't have a private gamedb for FDS, but the frontend
// expects this to be set.
RomStatus = game.Status;
}
PutSettings((NESSettings)settings ?? new NESSettings());
2016-10-26 23:29:10 +00:00
// we need to put this here because the line directly above will overwrite palette intialization anywhere else
2016-10-30 16:26:32 +00:00
// TODO: What if settings are later loaded?
2016-10-26 23:29:10 +00:00
if (_isVS)
{
PickVSPalette(cart);
2016-10-26 23:29:10 +00:00
}
ser.Register<IDisassemblable>(cpu);
2016-02-28 13:28:00 +00:00
Tracer = new TraceBuffer { Header = cpu.TraceHeader };
ser.Register<ITraceable>(Tracer);
ser.Register<IVideoProvider>(videoProvider);
ser.Register<ISoundProvider>(magicSoundProvider);
if (Board is BANDAI_FCG_1)
{
var reader = (Board as BANDAI_FCG_1).reader;
// not all BANDAI FCG 1 boards have a barcode reader
if (reader != null)
ser.Register<DatachBarcode>(reader);
}
}
static readonly bool USE_DATABASE = true;
public RomStatus RomStatus;
public IEmulatorServiceProvider ServiceProvider { get; private set; }
2011-09-24 17:07:48 +00:00
private NES()
{
BootGodDB.Initialize();
}
2011-06-07 01:05:57 +00:00
public void WriteLogTimestamp()
{
if (ppu != null)
Console.Write("[{0:d5}:{1:d3}:{2:d3}]", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle);
2011-06-07 01:05:57 +00:00
}
public void LogLine(string format, params object[] args)
{
if (ppu != null)
Console.WriteLine("[{0:d5}:{1:d3}:{2:d3}] {3}", Frame, ppu.ppur.status.sl, ppu.ppur.status.cycle, string.Format(format, args));
2011-06-07 01:05:57 +00:00
}
2015-08-07 21:15:50 +00:00
public bool HasMapperProperties
{
get
{
var fields = Board.GetType().GetFields();
foreach (var field in fields)
{
var attrib = field.GetCustomAttributes(typeof(MapperPropAttribute), false).OfType<MapperPropAttribute>().SingleOrDefault();
if (attrib != null)
{
return true;
}
}
return false;
}
}
2016-10-30 02:35:46 +00:00
public bool IsVS
{
get { return _isVS; }
}
public bool IsFDS
{
get { return Board is FDS; }
}
NESWatch GetWatch(NESWatch.EDomain domain, int address)
{
if (domain == NESWatch.EDomain.Sysbus)
{
NESWatch ret = sysbus_watch[address] ?? new NESWatch(this, domain, address);
sysbus_watch[address] = ret;
return ret;
}
return null;
}
class NESWatch
{
public enum EDomain
{
Sysbus
}
public NESWatch(NES nes, EDomain domain, int address)
{
Address = address;
Domain = domain;
if (domain == EDomain.Sysbus)
{
watches = nes.sysbus_watch;
}
}
public int Address;
public EDomain Domain;
public enum EFlags
{
None = 0,
GameGenie = 1,
ReadPrint = 2
}
EFlags flags;
public void Sync()
{
if (flags == EFlags.None)
watches[Address] = null;
else watches[Address] = this;
}
public void SetGameGenie(byte? compare, byte value)
{
flags |= EFlags.GameGenie;
Compare = compare;
Value = value;
Sync();
}
public bool HasGameGenie
{
get
{
return (flags & EFlags.GameGenie) != 0;
}
}
public byte ApplyGameGenie(byte curr)
{
if (!HasGameGenie)
{
return curr;
}
else if (curr == Compare || Compare == null)
{
Console.WriteLine("applied game genie");
return (byte)Value;
}
else
{
return curr;
}
}
public void RemoveGameGenie()
{
flags &= ~EFlags.GameGenie;
Sync();
}
byte? Compare;
byte Value;
NESWatch[] watches;
}
public CoreComm CoreComm { get; private set; }
public DisplayType Region { get { return _display_type; } }
2011-02-20 02:49:37 +00:00
class MyVideoProvider : IVideoProvider
{
//public int ntsc_top = 8;
//public int ntsc_bottom = 231;
//public int pal_top = 0;
//public int pal_bottom = 239;
public int left = 0;
public int right = 255;
2011-02-20 02:49:37 +00:00
NES emu;
public MyVideoProvider(NES emu)
{
this.emu = emu;
}
2011-03-21 01:49:20 +00:00
int[] pixels = new int[256 * 240];
2011-02-20 02:49:37 +00:00
public int[] GetVideoBuffer()
{
return pixels;
}
public void FillFrameBuffer()
2011-02-20 02:49:37 +00:00
{
int the_top;
int the_bottom;
if (emu.Region == DisplayType.NTSC)
{
the_top = emu.Settings.NTSC_TopLine;
the_bottom = emu.Settings.NTSC_BottomLine;
}
else
{
the_top = emu.Settings.PAL_TopLine;
the_bottom = emu.Settings.PAL_BottomLine;
}
int backdrop = 0;
backdrop = emu.Settings.BackgroundColor;
bool useBackdrop = (backdrop & 0xFF000000) != 0;
2011-09-24 17:07:48 +00:00
2014-05-14 15:46:16 +00:00
if (useBackdrop)
2011-03-21 01:49:20 +00:00
{
2014-05-14 15:46:16 +00:00
int width = BufferWidth;
for (int x = left; x <= right; x++)
{
2014-05-14 15:46:16 +00:00
for (int y = the_top; y <= the_bottom; y++)
{
2014-05-14 15:46:16 +00:00
short pixel = emu.ppu.xbuf[(y << 8) + x];
if ((pixel & 0x8000) != 0 && useBackdrop)
{
pixels[((y - the_top) * width) + (x - left)] = backdrop;
}
else pixels[((y - the_top) * width) + (x - left)] = emu.palette_compiled[pixel & 0x7FFF];
}
}
}
else
{
unsafe
{
fixed (int* dst_ = pixels)
fixed (short* src_ = emu.ppu.xbuf)
fixed (int* pal = emu.palette_compiled)
{
int* dst = dst_;
short* src = src_ + 256 * the_top + left;
int xcount = right - left + 1;
int srcinc = 256 - xcount;
int ycount = the_bottom - the_top + 1;
xcount /= 16;
for (int y = 0; y < ycount; y++)
{
for (int x = 0; x < xcount; x++)
{
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
*dst++ = pal[0x7fff & *src++];
}
src += srcinc;
}
}
}
2011-03-21 01:49:20 +00:00
}
2011-02-20 02:49:37 +00:00
}
public int VirtualWidth { get { return (int)(BufferWidth * 1.146); } }
public int VirtualHeight { get { return BufferHeight; } }
public int BufferWidth { get { return right - left + 1; } }
2011-02-20 02:49:37 +00:00
public int BackgroundColor { get { return 0; } }
public int BufferHeight
{
get
{
if (emu.Region == DisplayType.NTSC)
{
return emu.Settings.NTSC_BottomLine - emu.Settings.NTSC_TopLine + 1;
}
else
{
return emu.Settings.PAL_BottomLine - emu.Settings.PAL_TopLine + 1;
}
}
}
2017-05-05 16:25:38 +00:00
public int VsyncNumerator => emu.VsyncNum;
public int VsyncDenominator => emu.VsyncDen;
2011-02-20 02:49:37 +00:00
}
2011-03-21 01:51:06 +00:00
MyVideoProvider videoProvider;
2011-02-20 02:49:37 +00:00
[Obsolete] // with the changes to both nes and quicknes cores, nothing uses this anymore
2011-02-20 02:49:37 +00:00
public static readonly ControllerDefinition NESController =
new ControllerDefinition
{
Name = "NES Controller",
BoolButtons = {
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Start", "P1 Select", "P1 B", "P1 A", "Reset", "Power",
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Start", "P2 Select", "P2 B", "P2 A"
}
2011-02-20 02:49:37 +00:00
};
public ControllerDefinition ControllerDefinition { get; private set; }
2011-02-20 02:49:37 +00:00
private int _frame;
public int Frame { get { return _frame; } set { _frame = value; } }
public void ResetCounters()
{
_frame = 0;
_lagcount = 0;
islag = false;
}
public long Timestamp { get; private set; }
public bool DeterministicEmulation { get { return true; } }
2011-02-20 02:49:37 +00:00
public string SystemId { get { return "NES"; } }
public string GameName { get { return game_name; } }
public enum EDetectionOrigin
2011-03-19 20:12:06 +00:00
{
None, BootGodDB, GameDB, INES, UNIF, FDS, NSF
2011-03-19 20:12:06 +00:00
}
StringWriter LoadReport;
void LoadWriteLine(string format, params object[] arg)
{
Console.WriteLine(format, arg);
LoadReport.WriteLine(format, arg);
}
void LoadWriteLine(object arg) { LoadWriteLine("{0}", arg); }
2011-09-24 17:07:48 +00:00
class MyWriter : StringWriter
{
public MyWriter(TextWriter _loadReport)
{
loadReport = _loadReport;
}
TextWriter loadReport;
public override void WriteLine(string format, params object[] arg)
{
Console.WriteLine(format, arg);
loadReport.WriteLine(format, arg);
}
2012-12-03 15:40:20 +00:00
public override void WriteLine(string value)
{
Console.WriteLine(value);
loadReport.WriteLine(value);
}
}
2014-07-02 15:21:42 +00:00
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;
2012-11-06 03:05:43 +00:00
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:");
2014-07-23 15:45:30 +00:00
LoadWriteLine(unif.CartInfo);
LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash.");
2014-07-23 15:45:30 +00:00
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;
}
2013-12-08 21:39:17 +00:00
else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A"))
|| file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI")))
{
2014-07-02 15:21:42 +00:00
// 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);
2014-07-02 15:21:42 +00:00
// 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);
2014-07-02 15:21:42 +00:00
Console.WriteLine("Using NTSC display type for FDS disk image");
_display_type = Common.DisplayType.NTSC;
HardReset();
2014-07-02 15:21:42 +00:00
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.
2012-03-06 07:51:41 +00:00
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
2017-06-07 00:04:59 +00:00
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
}
2017-06-07 00:04:59 +00:00
else if (file.Length >= (8 * 1024 + iNesHeaderInfo.chr_size * 1024 + 16))
{
2017-06-07 00:04:59 +00:00
// maybe the PRG is only 8k
msTemp.Write(file, 16 + 8 * 1024, iNesHeaderInfo.chr_size * 1024); //add chr
}
2017-06-07 00:04:59 +00:00
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);
}
2012-03-06 07:51:41 +00:00
}
}
2012-03-06 07:51:41 +00:00
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)
{
2014-07-23 15:45:30 +00:00
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");
2014-07-23 15:45:30 +00:00
choice = unif.CartInfo;
2014-12-14 00:16:05 +00:00
//ok, i have this Q-Boy rom with no VROM and no VRAM.
2015-08-18 21:37:34 +00:00
//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;
2014-12-14 00:16:05 +00:00
//(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)
{
2014-04-09 22:23:19 +00:00
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;
}
}
2014-05-23 15:10:14 +00:00
game_name = choice.name;
//find a INESBoard to handle this
2014-04-09 22:23:19 +00:00
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);
2011-03-19 20:12:06 +00:00
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();
2014-07-02 15:21:42 +00:00
// 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);
2012-06-23 08:52:12 +00:00
// 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:
2017-04-27 16:45:44 +00:00
_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]);
}
}
2011-03-01 09:32:12 +00:00
2014-07-02 15:21:42 +00:00
static NESSyncSettings.Region DetectRegion(string system)
{
switch (system)
{
case "NES-PAL":
case "NES-PAL-A":
case "NES-PAL-B":
return NESSyncSettings.Region.PAL;
case "NES-NTSC":
case "Famicom":
return NESSyncSettings.Region.NTSC;
// this is in bootgod, but not used at all
case "Dendy":
return NESSyncSettings.Region.Dendy;
case null:
Console.WriteLine("Rom is of unknown NES region!");
return NESSyncSettings.Region.Default;
default:
Console.WriteLine("Unrecognized region {0}", system);
return NESSyncSettings.Region.Default;
}
}
private ITraceable Tracer { get; set; }
}
}
//todo
//http://blog.ntrq.net/?p=428
//A VERY NICE board assignments list
//http://personales.epsg.upv.es/~jogilmo1/nes/TEXTOS/ARXIUS/BOARDTABLE.TXT
//why not make boards communicate over the actual board pinouts
//http://wiki.nesdev.com/w/index.php/Cartridge_connector
//a mappers list
//http://tuxnes.sourceforge.net/nesmapper.txt