2011-02-20 02:49:37 +00:00
|
|
|
|
using System;
|
2011-03-02 06:18:26 +00:00
|
|
|
|
using System.Linq;
|
2011-02-20 02:49:37 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
2013-11-04 00:36:15 +00:00
|
|
|
|
using BizHawk.Common;
|
2014-07-03 19:20:34 +00:00
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
|
|
2013-11-04 01:06:36 +00:00
|
|
|
|
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
|
|
|
|
{
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"NesHawk",
|
|
|
|
|
"zeromus, natt, adelikat",
|
|
|
|
|
isPorted: false,
|
|
|
|
|
isReleased: true
|
|
|
|
|
)]
|
2014-01-06 21:50:52 +00:00
|
|
|
|
public partial class NES : IEmulator
|
2011-02-20 02:49:37 +00:00
|
|
|
|
{
|
2011-09-03 17:13:42 +00:00
|
|
|
|
static readonly bool USE_DATABASE = true;
|
|
|
|
|
public RomStatus RomStatus;
|
2011-06-08 01:03:32 +00:00
|
|
|
|
|
2014-01-01 03:03:10 +00:00
|
|
|
|
public NES(CoreComm comm, GameInfo game, byte[] rom, object Settings, object SyncSettings)
|
2011-02-28 06:16:20 +00:00
|
|
|
|
{
|
2013-12-10 17:58:12 +00:00
|
|
|
|
byte[] fdsbios = comm.CoreFileProvider.GetFirmware("NES", "Bios_FDS", false);
|
2013-12-09 20:36:24 +00:00
|
|
|
|
if (fdsbios != null && fdsbios.Length == 40976)
|
|
|
|
|
{
|
2013-12-10 17:58:12 +00:00
|
|
|
|
comm.ShowMessage("Your FDS BIOS is a bad dump. BizHawk will attempt to use it, but no guarantees! You should find a new one.");
|
2013-12-09 20:36:24 +00:00
|
|
|
|
var tmp = new byte[8192];
|
|
|
|
|
Buffer.BlockCopy(fdsbios, 16 + 8192 * 3, tmp, 0, 8192);
|
|
|
|
|
fdsbios = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-01 03:03:10 +00:00
|
|
|
|
this.SyncSettings = (NESSyncSettings)SyncSettings ?? new NESSyncSettings();
|
2014-03-04 23:18:10 +00:00
|
|
|
|
this.ControllerSettings = this.SyncSettings.Controls;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm = comm;
|
|
|
|
|
CoreComm.CpuTraceAvailable = true;
|
2011-03-08 07:25:35 +00:00
|
|
|
|
BootGodDB.Initialize();
|
2011-03-21 01:49:20 +00:00
|
|
|
|
videoProvider = new MyVideoProvider(this);
|
2012-10-21 15:58:24 +00:00
|
|
|
|
Init(game, rom, fdsbios);
|
2012-10-26 18:51:08 +00:00
|
|
|
|
if (board is FDS)
|
|
|
|
|
{
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.UsesDriveLed = true;
|
2014-02-28 04:05:36 +00:00
|
|
|
|
(board as FDS).SetDriveLightCallback((val) => CoreComm.DriveLED = val);
|
2012-10-26 18:51:08 +00:00
|
|
|
|
}
|
2013-12-24 22:37:45 +00:00
|
|
|
|
PutSettings(Settings ?? new NESSettings());
|
2011-02-28 06:16:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-09-24 17:07:48 +00:00
|
|
|
|
private NES()
|
|
|
|
|
{
|
|
|
|
|
BootGodDB.Initialize();
|
|
|
|
|
}
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2011-06-07 01:05:57 +00:00
|
|
|
|
public void WriteLogTimestamp()
|
|
|
|
|
{
|
2011-06-09 19:45:07 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (ppu != null)
|
2011-06-09 19:45:07 +00:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2011-03-17 03:51:31 +00:00
|
|
|
|
NESWatch GetWatch(NESWatch.EDomain domain, int address)
|
|
|
|
|
{
|
|
|
|
|
if (domain == NESWatch.EDomain.Sysbus)
|
|
|
|
|
{
|
2011-07-30 20:49:36 +00:00
|
|
|
|
NESWatch ret = sysbus_watch[address] ?? new NESWatch(this, domain, address);
|
2011-03-17 03:51:31 +00:00
|
|
|
|
sysbus_watch[address] = ret;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-16 05:06:21 +00:00
|
|
|
|
class NESWatch
|
|
|
|
|
{
|
2011-03-17 03:51:31 +00:00
|
|
|
|
public enum EDomain
|
|
|
|
|
{
|
|
|
|
|
Sysbus
|
|
|
|
|
}
|
2012-09-01 14:13:36 +00:00
|
|
|
|
|
2011-03-17 03:51:31 +00:00
|
|
|
|
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;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
2011-03-17 03:51:31 +00:00
|
|
|
|
public void Sync()
|
|
|
|
|
{
|
|
|
|
|
if (flags == EFlags.None)
|
|
|
|
|
watches[Address] = null;
|
|
|
|
|
else watches[Address] = this;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-01 14:13:36 +00:00
|
|
|
|
public void SetGameGenie(byte? compare, byte value)
|
2011-03-17 03:51:31 +00:00
|
|
|
|
{
|
|
|
|
|
flags |= EFlags.GameGenie;
|
2012-09-01 14:13:36 +00:00
|
|
|
|
Compare = compare;
|
|
|
|
|
Value = value;
|
2011-03-17 03:51:31 +00:00
|
|
|
|
Sync();
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-01 14:13:36 +00:00
|
|
|
|
public bool HasGameGenie
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return (flags & EFlags.GameGenie) != 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-28 04:05:36 +00:00
|
|
|
|
|
2011-03-17 03:51:31 +00:00
|
|
|
|
public byte ApplyGameGenie(byte curr)
|
|
|
|
|
{
|
2012-09-01 14:13:36 +00:00
|
|
|
|
if (!HasGameGenie)
|
|
|
|
|
{
|
|
|
|
|
return curr;
|
|
|
|
|
}
|
|
|
|
|
else if (curr == Compare || Compare == null)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("applied game genie");
|
|
|
|
|
return (byte)Value;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return curr;
|
|
|
|
|
}
|
2011-03-17 03:51:31 +00:00
|
|
|
|
}
|
2011-03-16 05:06:21 +00:00
|
|
|
|
|
2011-03-17 03:51:31 +00:00
|
|
|
|
public void RemoveGameGenie()
|
2011-03-16 05:06:21 +00:00
|
|
|
|
{
|
2011-03-17 03:51:31 +00:00
|
|
|
|
flags &= ~EFlags.GameGenie;
|
|
|
|
|
Sync();
|
2011-03-16 05:06:21 +00:00
|
|
|
|
}
|
2011-03-17 03:51:31 +00:00
|
|
|
|
|
2012-09-01 14:13:36 +00:00
|
|
|
|
byte? Compare;
|
|
|
|
|
byte Value;
|
2011-03-17 03:51:31 +00:00
|
|
|
|
|
|
|
|
|
NESWatch[] watches;
|
2011-03-16 05:06:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public CoreComm CoreComm { get; private set; }
|
2011-06-11 22:15:08 +00:00
|
|
|
|
|
2013-03-25 01:59:34 +00:00
|
|
|
|
public DisplayType DisplayType { get { return _display_type; } }
|
2012-10-06 16:56:46 +00:00
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
class MyVideoProvider : IVideoProvider
|
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
//public int ntsc_top = 8;
|
|
|
|
|
//public int ntsc_bottom = 231;
|
|
|
|
|
//public int pal_top = 0;
|
|
|
|
|
//public int pal_bottom = 239;
|
2011-09-04 01:58:16 +00:00
|
|
|
|
public int left = 0;
|
2012-03-04 01:41:14 +00:00
|
|
|
|
public int right = 255;
|
2014-02-28 04:05:36 +00:00
|
|
|
|
|
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()
|
2012-08-19 20:01:17 +00:00
|
|
|
|
{
|
|
|
|
|
return pixels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void FillFrameBuffer()
|
2011-02-20 02:49:37 +00:00
|
|
|
|
{
|
2013-03-25 01:59:34 +00:00
|
|
|
|
int the_top;
|
|
|
|
|
int the_bottom;
|
|
|
|
|
if (emu.DisplayType == DisplayType.NTSC)
|
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
the_top = emu.Settings.NTSC_TopLine;
|
|
|
|
|
the_bottom = emu.Settings.NTSC_BottomLine;
|
2013-03-25 01:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
the_top = emu.Settings.PAL_TopLine;
|
|
|
|
|
the_bottom = emu.Settings.PAL_BottomLine;
|
2013-03-25 01:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-20 00:53:21 +00:00
|
|
|
|
int backdrop = 0;
|
2013-12-22 00:44:39 +00:00
|
|
|
|
backdrop = emu.Settings.BackgroundColor;
|
2011-06-11 22:15:08 +00:00
|
|
|
|
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++)
|
2011-06-11 22:15:08 +00:00
|
|
|
|
{
|
2014-05-14 15:46:16 +00:00
|
|
|
|
for (int y = the_top; y <= the_bottom; y++)
|
2011-09-04 01:12:12 +00:00
|
|
|
|
{
|
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-09-04 01:12:12 +00:00
|
|
|
|
}
|
2011-06-11 22:15:08 +00:00
|
|
|
|
}
|
2011-03-21 01:49:20 +00:00
|
|
|
|
}
|
2011-02-20 02:49:37 +00:00
|
|
|
|
}
|
2014-04-30 23:48:37 +00:00
|
|
|
|
public int VirtualWidth { get { return (int)(BufferWidth * 1.146); } }
|
|
|
|
|
public int VirtualHeight { get { return BufferHeight; } }
|
2012-03-04 01:41:14 +00:00
|
|
|
|
public int BufferWidth { get { return right - left + 1; } }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public int BackgroundColor { get { return 0; } }
|
2013-03-25 01:59:34 +00:00
|
|
|
|
public int BufferHeight
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (emu.DisplayType == DisplayType.NTSC)
|
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
return emu.Settings.NTSC_BottomLine - emu.Settings.NTSC_TopLine + 1;
|
2013-03-25 01:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
return emu.Settings.PAL_BottomLine - emu.Settings.PAL_TopLine + 1;
|
2013-03-25 01:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-28 04:05:36 +00:00
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-21 01:51:06 +00:00
|
|
|
|
MyVideoProvider videoProvider;
|
|
|
|
|
public IVideoProvider VideoProvider { get { return videoProvider; } }
|
2012-09-29 22:19:49 +00:00
|
|
|
|
public ISoundProvider SoundProvider { get { return magicSoundProvider; } }
|
2012-12-09 03:13:47 +00:00
|
|
|
|
public ISyncSoundProvider SyncSoundProvider { get { return magicSoundProvider; } }
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public bool StartAsyncSound() { return true; }
|
|
|
|
|
public void EndAsyncSound() { }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
|
|
|
|
|
public static readonly ControllerDefinition NESController =
|
|
|
|
|
new ControllerDefinition
|
|
|
|
|
{
|
2012-02-19 07:09:24 +00:00
|
|
|
|
Name = "NES Controller",
|
|
|
|
|
BoolButtons = {
|
2013-07-29 02:11:00 +00:00
|
|
|
|
"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"
|
2012-02-19 07:09:24 +00:00
|
|
|
|
}
|
2011-02-20 02:49:37 +00:00
|
|
|
|
};
|
|
|
|
|
|
2012-10-26 18:51:08 +00:00
|
|
|
|
public ControllerDefinition ControllerDefinition { get; private set; }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
|
|
|
|
|
IController controller;
|
|
|
|
|
public IController Controller
|
|
|
|
|
{
|
|
|
|
|
get { return controller; }
|
|
|
|
|
set { controller = value; }
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-17 22:51:53 +00:00
|
|
|
|
int _frame;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
int _lagcount;
|
|
|
|
|
bool lagged = true;
|
|
|
|
|
bool islag = false;
|
2011-04-17 22:51:53 +00:00
|
|
|
|
public int Frame { get { return _frame; } set { _frame = value; } }
|
2011-07-30 20:49:36 +00:00
|
|
|
|
|
2013-11-03 16:29:51 +00:00
|
|
|
|
public void ResetCounters()
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
|
|
|
|
_frame = 0;
|
2012-11-25 15:41:40 +00:00
|
|
|
|
_lagcount = 0;
|
|
|
|
|
islag = false;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 07:14:34 +00:00
|
|
|
|
public long Timestamp { get; private set; }
|
2011-07-30 20:49:36 +00:00
|
|
|
|
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
|
|
|
|
public bool IsLagFrame { get { return islag; } }
|
2011-03-01 14:38:52 +00:00
|
|
|
|
|
2012-10-03 15:31:04 +00:00
|
|
|
|
public bool DeterministicEmulation { get { return true; } }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
|
2012-09-14 22:28:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public byte[] ReadSaveRam()
|
2011-03-01 07:25:14 +00:00
|
|
|
|
{
|
2012-10-27 14:01:55 +00:00
|
|
|
|
if (board is FDS)
|
|
|
|
|
return (board as FDS).ReadSaveRam();
|
|
|
|
|
|
2012-09-14 22:28:38 +00:00
|
|
|
|
if (board == null || board.SaveRam == null)
|
|
|
|
|
return null;
|
2014-02-28 04:05:36 +00:00
|
|
|
|
return (byte[])board.SaveRam.Clone();
|
2012-09-14 22:28:38 +00:00
|
|
|
|
}
|
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
|
|
|
{
|
2012-10-27 14:01:55 +00:00
|
|
|
|
if (board is FDS)
|
|
|
|
|
{
|
|
|
|
|
(board as FDS).StoreSaveRam(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-14 22:28:38 +00:00
|
|
|
|
if (board == null || board.SaveRam == null)
|
|
|
|
|
return;
|
|
|
|
|
Array.Copy(data, board.SaveRam, data.Length);
|
2011-03-01 07:25:14 +00:00
|
|
|
|
}
|
2012-09-14 22:28:38 +00:00
|
|
|
|
|
|
|
|
|
public void ClearSaveRam()
|
|
|
|
|
{
|
2012-10-27 14:01:55 +00:00
|
|
|
|
if (board is FDS)
|
|
|
|
|
{
|
|
|
|
|
(board as FDS).ClearSaveRam();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-14 22:28:38 +00:00
|
|
|
|
if (board == null || board.SaveRam == null)
|
|
|
|
|
return;
|
|
|
|
|
for (int i = 0; i < board.SaveRam.Length; i++)
|
|
|
|
|
board.SaveRam[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public bool SaveRamModified
|
|
|
|
|
{
|
2012-10-27 14:01:55 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (board == null) return false;
|
|
|
|
|
if (board is FDS) return true;
|
|
|
|
|
if (board.SaveRam == null) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2011-02-20 02:49:37 +00:00
|
|
|
|
set { }
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
private MemoryDomainList memoryDomains;
|
2011-03-05 03:03:47 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
private void SetupMemoryDomains()
|
|
|
|
|
{
|
|
|
|
|
var domains = new List<MemoryDomain>();
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var RAM = new MemoryDomain("RAM", 0x800, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => ram[addr], (addr, value) => ram[addr] = value);
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var SystemBus = new MemoryDomain("System Bus", 0x10000, MemoryDomain.Endian.Little,
|
2014-02-27 20:58:00 +00:00
|
|
|
|
addr => PeekMemory((ushort)addr), (addr, value) => ApplySystemBusPoke(addr, value));
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var PPUBus = new MemoryDomain("PPU Bus", 0x4000, MemoryDomain.Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => ppu.ppubus_peek(addr), (addr, value) => ppu.ppubus_write(addr, value));
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var CIRAMdomain = new MemoryDomain("CIRAM (nametables)", 0x800, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => CIRAM[addr], (addr, value) => CIRAM[addr] = value);
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var OAMdoman = new MemoryDomain("OAM", 64 * 4, MemoryDomain.Endian.Unknown,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => ppu.OAM[addr], (addr, value) => ppu.OAM[addr] = value);
|
2011-03-16 03:13:51 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
domains.Add(RAM);
|
2011-03-08 07:25:35 +00:00
|
|
|
|
domains.Add(SystemBus);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
domains.Add(PPUBus);
|
2011-03-16 03:13:51 +00:00
|
|
|
|
domains.Add(CIRAMdomain);
|
2011-08-28 19:07:33 +00:00
|
|
|
|
domains.Add(OAMdoman);
|
2011-03-06 02:36:49 +00:00
|
|
|
|
|
2012-10-27 14:01:55 +00:00
|
|
|
|
if (!(board is FDS) && board.SaveRam != null)
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var BatteryRam = new MemoryDomain("Battery RAM", board.SaveRam.Length, MemoryDomain.Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => board.SaveRam[addr], (addr, value) => board.SaveRam[addr] = value);
|
|
|
|
|
domains.Add(BatteryRam);
|
|
|
|
|
}
|
2011-03-06 03:07:25 +00:00
|
|
|
|
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var PRGROM = new MemoryDomain("PRG ROM", cart.prg_size * 1024, MemoryDomain.Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => board.ROM[addr], (addr, value) => board.ROM[addr] = value);
|
|
|
|
|
domains.Add(PRGROM);
|
2011-03-06 03:34:13 +00:00
|
|
|
|
|
2011-03-08 07:25:35 +00:00
|
|
|
|
if (board.VROM != null)
|
2011-07-30 20:49:36 +00:00
|
|
|
|
{
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var CHRROM = new MemoryDomain("CHR VROM", cart.chr_size * 1024, MemoryDomain.Endian.Little,
|
2011-03-07 10:41:46 +00:00
|
|
|
|
addr => board.VROM[addr], (addr, value) => board.VROM[addr] = value);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
domains.Add(CHRROM);
|
|
|
|
|
}
|
2011-03-06 03:34:13 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (board.VRAM != null)
|
|
|
|
|
{
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var VRAM = new MemoryDomain("VRAM", board.VRAM.Length, MemoryDomain.Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => board.VRAM[addr], (addr, value) => board.VRAM[addr] = value);
|
2011-03-08 07:25:35 +00:00
|
|
|
|
domains.Add(VRAM);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
}
|
2011-03-06 04:40:56 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (board.WRAM != null)
|
|
|
|
|
{
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var WRAM = new MemoryDomain("WRAM", board.WRAM.Length, MemoryDomain.Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => board.WRAM[addr], (addr, value) => board.WRAM[addr] = value);
|
2011-03-08 07:25:35 +00:00
|
|
|
|
domains.Add(WRAM);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
}
|
2011-03-06 04:40:56 +00:00
|
|
|
|
|
2012-10-29 17:11:19 +00:00
|
|
|
|
// if there were more boards with special ram sets, we'd want to do something more general
|
2012-10-26 21:25:20 +00:00
|
|
|
|
if (board is FDS)
|
|
|
|
|
domains.Add((board as FDS).GetDiskPeeker());
|
2012-10-29 17:11:19 +00:00
|
|
|
|
else if (board is ExROM)
|
|
|
|
|
domains.Add((board as ExROM).GetExRAM());
|
2012-10-26 21:25:20 +00:00
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
memoryDomains = new MemoryDomainList(domains);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
}
|
2011-03-05 03:03:47 +00:00
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public string SystemId { get { return "NES"; } }
|
2013-11-06 02:15:29 +00:00
|
|
|
|
public MemoryDomainList MemoryDomains { get { return memoryDomains; } }
|
2011-02-21 09:48:53 +00:00
|
|
|
|
|
2011-03-07 10:41:46 +00:00
|
|
|
|
public string GameName { get { return game_name; } }
|
|
|
|
|
|
2011-03-20 02:12:10 +00:00
|
|
|
|
public enum EDetectionOrigin
|
2011-03-19 20:12:06 +00:00
|
|
|
|
{
|
2012-10-21 15:58:24 +00:00
|
|
|
|
None, BootGodDB, GameDB, INES, UNIF, FDS
|
2011-03-19 20:12:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
StringWriter LoadReport;
|
|
|
|
|
void LoadWriteLine(string format, params object[] arg)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(format, arg);
|
|
|
|
|
LoadReport.WriteLine(format, arg);
|
|
|
|
|
}
|
2011-07-30 20:49:36 +00:00
|
|
|
|
void LoadWriteLine(object arg) { LoadWriteLine("{0}", arg); }
|
2011-09-24 17:07:48 +00:00
|
|
|
|
|
2012-03-06 08:01:48 +00:00
|
|
|
|
class MyWriter : StringWriter
|
|
|
|
|
{
|
2014-02-28 04:05:36 +00:00
|
|
|
|
public MyWriter(TextWriter _loadReport)
|
2012-03-06 08:01:48 +00:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2012-03-06 08:01:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-02 15:21:42 +00:00
|
|
|
|
public void Init(GameInfo gameInfo, byte[] rom, byte[] fdsbios = null)
|
2011-02-27 09:45:50 +00:00
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadReport = new StringWriter();
|
|
|
|
|
LoadWriteLine("------");
|
|
|
|
|
LoadWriteLine("BEGIN NES rom analysis:");
|
2011-08-04 03:20:54 +00:00
|
|
|
|
byte[] file = rom;
|
2012-10-16 22:27:48 +00:00
|
|
|
|
|
|
|
|
|
Type boardType = null;
|
|
|
|
|
CartInfo choice = null;
|
|
|
|
|
CartInfo iNesHeaderInfo = null;
|
2014-04-09 18:13:19 +00:00
|
|
|
|
CartInfo iNesHeaderInfoV2 = null;
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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
|
|
|
|
|
2014-01-01 03:03:10 +00:00
|
|
|
|
Dictionary<string, string> InitialMapperRegisterValues = new Dictionary<string, string>(SyncSettings.BoardProperties);
|
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
origin = EDetectionOrigin.None;
|
|
|
|
|
|
2011-03-02 06:18:26 +00:00
|
|
|
|
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")))
|
2011-02-27 09:45:50 +00:00
|
|
|
|
{
|
2014-04-09 19:39:04 +00:00
|
|
|
|
unif = new Unif(new MemoryStream(file));
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("Found UNIF header:");
|
2014-07-23 15:45:30 +00:00
|
|
|
|
LoadWriteLine(unif.CartInfo);
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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;
|
2012-10-16 22:27:48 +00:00
|
|
|
|
hash_sha1_several.Add(hash_sha1);
|
2012-10-17 00:59:22 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_sha1);
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
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")))
|
2012-10-21 15:58:24 +00:00
|
|
|
|
{
|
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
|
2012-10-21 15:58:24 +00:00
|
|
|
|
origin = EDetectionOrigin.FDS;
|
|
|
|
|
LoadWriteLine("Found FDS header.");
|
|
|
|
|
if (fdsbios == null)
|
|
|
|
|
throw new Exception("Missing FDS Bios!");
|
|
|
|
|
cart = new CartInfo();
|
|
|
|
|
var fdsboard = new FDS();
|
|
|
|
|
fdsboard.biosrom = fdsbios;
|
2012-10-27 14:01:55 +00:00
|
|
|
|
fdsboard.SetDiskImage(rom);
|
2012-10-21 15:58:24 +00:00
|
|
|
|
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;
|
2012-10-21 15:58:24 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2014-07-02 15:21:42 +00:00
|
|
|
|
Console.WriteLine("Using NTSC display type for FDS disk image");
|
|
|
|
|
_display_type = Common.DisplayType.NTSC;
|
|
|
|
|
|
2012-10-21 15:58:24 +00:00
|
|
|
|
HardReset();
|
2014-07-02 15:21:42 +00:00
|
|
|
|
|
2012-10-21 15:58:24 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2014-04-09 18:13:19 +00:00
|
|
|
|
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");
|
2011-02-27 09:45:50 +00:00
|
|
|
|
|
2014-04-09 18:13:19 +00:00
|
|
|
|
//now that we know we have an iNES header, we can try to ignore it.
|
2012-03-06 07:51:41 +00:00
|
|
|
|
|
2014-07-03 19:20:34 +00:00
|
|
|
|
hash_sha1 = "sha1:" + file.HashSHA1(16, file.Length - 16);
|
2014-04-09 18:13:19 +00:00
|
|
|
|
hash_sha1_several.Add(hash_sha1);
|
2014-07-03 19:20:34 +00:00
|
|
|
|
hash_md5 = "md5:" + file.HashMD5(16, file.Length - 16);
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2014-04-09 18:13:19 +00:00
|
|
|
|
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.");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2014-04-09 18:13:19 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_sha1);
|
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_md5);
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2014-04-09 18:13:19 +00:00
|
|
|
|
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();
|
2014-07-03 19:20:34 +00:00
|
|
|
|
var hash = "sha1:" + bytes.HashSHA1(0, bytes.Length);
|
2014-04-09 18:13:19 +00:00
|
|
|
|
LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash);
|
|
|
|
|
hash_sha1_several.Add(hash);
|
2014-07-03 19:20:34 +00:00
|
|
|
|
hash = "md5:" + bytes.HashMD5(0, bytes.Length);
|
2014-04-09 18:13:19 +00:00
|
|
|
|
LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash);
|
2012-03-06 07:51:41 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2012-03-06 07:51:41 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
if (USE_DATABASE)
|
|
|
|
|
{
|
2014-02-16 06:16:55 +00:00
|
|
|
|
if (hash_md5 != null) choice = IdentifyFromGameDB(hash_md5);
|
|
|
|
|
if (choice == null)
|
|
|
|
|
choice = IdentifyFromGameDB(hash_sha1);
|
2011-03-20 20:42:12 +00:00
|
|
|
|
if (choice == null)
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("Could not locate game in bizhawk gamedb");
|
2011-03-20 20:42:12 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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
|
2012-10-24 23:30:46 +00:00
|
|
|
|
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;
|
2012-10-24 23:30:46 +00:00
|
|
|
|
// 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;
|
|
|
|
|
}
|
2014-02-16 06:16:55 +00:00
|
|
|
|
}
|
2012-10-24 23:30:46 +00:00
|
|
|
|
|
2014-02-16 06:16:55 +00:00
|
|
|
|
//if this is still null, we have to try it some other way. nescartdb perhaps?
|
2014-02-28 04:05:36 +00:00
|
|
|
|
|
2014-02-16 06:16:55 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2011-03-20 20:42:12 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2014-02-16 06:16:55 +00:00
|
|
|
|
|
|
|
|
|
//if choice is still null, try UNIF and iNES
|
|
|
|
|
if (choice == null)
|
2012-10-16 22:27:48 +00:00
|
|
|
|
{
|
2014-02-16 06:16:55 +00:00
|
|
|
|
if (unif != null)
|
|
|
|
|
{
|
|
|
|
|
LoadWriteLine("Using information from UNIF header");
|
2014-07-23 15:45:30 +00:00
|
|
|
|
choice = unif.CartInfo;
|
2014-02-16 06:16:55 +00:00
|
|
|
|
origin = EDetectionOrigin.UNIF;
|
|
|
|
|
}
|
|
|
|
|
if (iNesHeaderInfo != null)
|
|
|
|
|
{
|
|
|
|
|
LoadWriteLine("Attempting inference from iNES header");
|
2014-04-09 18:13:19 +00:00
|
|
|
|
// try to spin up V2 header first, then V1 header
|
|
|
|
|
if (iNesHeaderInfoV2 != null)
|
2014-02-16 06:16:55 +00:00
|
|
|
|
{
|
2014-04-09 18:13:19 +00:00
|
|
|
|
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
|
2014-02-16 06:16:55 +00:00
|
|
|
|
}
|
|
|
|
|
if (boardType == null)
|
|
|
|
|
{
|
2014-04-09 22:23:19 +00:00
|
|
|
|
choice = iNesHeaderInfo; // we're out of options, really
|
|
|
|
|
boardType = FindBoard(iNesHeaderInfo, origin, InitialMapperRegisterValues);
|
2014-04-09 18:13:19 +00:00
|
|
|
|
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
|
2014-02-16 06:16:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-09 18:13:19 +00:00
|
|
|
|
LoadWriteLine("Chose board from iNES heuristics:");
|
|
|
|
|
LoadWriteLine(choice);
|
2014-02-16 06:16:55 +00:00
|
|
|
|
origin = EDetectionOrigin.INES;
|
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
|
2014-05-23 15:10:14 +00:00
|
|
|
|
game_name = choice.name;
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
//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");
|
2012-10-16 22:27:48 +00:00
|
|
|
|
if (boardType == null)
|
|
|
|
|
throw new Exception("No class implements the necessary board type: " + choice.board_type);
|
2011-03-19 20:12:06 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
if (choice.DB_GameInfo != null)
|
|
|
|
|
choice.bad = choice.DB_GameInfo.IsRomStatusBad();
|
2011-07-10 21:00:28 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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 ~~");
|
|
|
|
|
}
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("END NES rom analysis");
|
|
|
|
|
LoadWriteLine("------");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2012-10-17 00:38:28 +00:00
|
|
|
|
board = CreateBoardInstance(boardType);
|
2011-02-28 06:16:20 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
cart = choice;
|
|
|
|
|
board.Create(this);
|
2013-09-02 23:54:32 +00:00
|
|
|
|
board.InitialRegisterValues = InitialMapperRegisterValues;
|
2012-10-16 22:27:48 +00:00
|
|
|
|
board.Configure(origin);
|
2011-03-03 19:56:16 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
if (origin == EDetectionOrigin.BootGodDB)
|
|
|
|
|
{
|
|
|
|
|
RomStatus = RomStatus.GoodDump;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.RomStatusAnnotation = "Identified from BootGod's database";
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.UNIF)
|
|
|
|
|
{
|
|
|
|
|
RomStatus = RomStatus.NotInDatabase;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious";
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.INES)
|
|
|
|
|
{
|
|
|
|
|
RomStatus = RomStatus.NotInDatabase;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong";
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.GameDB)
|
|
|
|
|
{
|
|
|
|
|
if (choice.bad)
|
2011-07-10 21:00:28 +00:00
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
RomStatus = RomStatus.BadDump;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
else
|
2011-07-10 21:00:28 +00:00
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
RomStatus = choice.DB_GameInfo.Status;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2011-07-10 21:00:28 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadReport.Flush();
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.RomStatusDetails = LoadReport.ToString();
|
2011-07-10 21:00:28 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
//create the board's rom and vrom
|
|
|
|
|
if (iNesHeaderInfo != null)
|
|
|
|
|
{
|
|
|
|
|
//pluck the necessary bytes out of the file
|
2011-03-08 07:25:35 +00:00
|
|
|
|
board.ROM = new byte[choice.prg_size * 1024];
|
|
|
|
|
Array.Copy(file, 16, board.ROM, 0, board.ROM.Length);
|
|
|
|
|
if (choice.chr_size > 0)
|
2011-03-01 07:25:14 +00:00
|
|
|
|
{
|
2011-03-08 07:25:35 +00:00
|
|
|
|
board.VROM = new byte[choice.chr_size * 1024];
|
2012-03-06 07:51:41 +00:00
|
|
|
|
int vrom_offset = iNesHeaderInfo.prg_size * 1024;
|
2014-01-21 22:29:51 +00:00
|
|
|
|
// 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));
|
2011-03-01 07:25:14 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-07-23 15:45:30 +00:00
|
|
|
|
board.ROM = unif.PRG;
|
|
|
|
|
board.VROM = unif.CHR;
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2011-03-08 07:25:35 +00:00
|
|
|
|
|
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)
|
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
//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];
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
board.PostConfigure();
|
2012-06-23 08:52:12 +00:00
|
|
|
|
|
2014-02-06 02:06:17 +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:
|
|
|
|
|
_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);
|
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
HardReset();
|
2011-02-27 09:45:50 +00:00
|
|
|
|
}
|
2011-03-01 09:32:12 +00:00
|
|
|
|
|
2014-07-02 15:21:42 +00:00
|
|
|
|
static NESSyncSettings.Region DetectRegion(string system)
|
2014-02-06 02:06:17 +00:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-17 22:51:53 +00:00
|
|
|
|
void SyncState(Serializer ser)
|
2011-03-01 09:32:12 +00:00
|
|
|
|
{
|
2014-05-26 01:29:00 +00:00
|
|
|
|
int version = 4;
|
2011-04-17 22:51:53 +00:00
|
|
|
|
ser.BeginSection("NES");
|
2012-09-29 08:39:59 +00:00
|
|
|
|
ser.Sync("version", ref version);
|
2011-04-17 22:51:53 +00:00
|
|
|
|
ser.Sync("Frame", ref _frame);
|
2011-07-30 20:49:36 +00:00
|
|
|
|
ser.Sync("Lag", ref _lagcount);
|
2012-07-30 14:42:52 +00:00
|
|
|
|
ser.Sync("IsLag", ref islag);
|
2011-04-17 22:51:53 +00:00
|
|
|
|
cpu.SyncState(ser);
|
|
|
|
|
ser.Sync("ram", ref ram, false);
|
|
|
|
|
ser.Sync("CIRAM", ref CIRAM, false);
|
|
|
|
|
ser.Sync("cpu_accumulate", ref cpu_accumulate);
|
2011-05-20 18:55:01 +00:00
|
|
|
|
ser.Sync("_irq_apu", ref _irq_apu);
|
2012-03-25 09:25:27 +00:00
|
|
|
|
ser.Sync("sprdma_countdown", ref sprdma_countdown);
|
|
|
|
|
ser.Sync("cpu_step", ref cpu_step);
|
|
|
|
|
ser.Sync("cpu_stepcounter", ref cpu_stepcounter);
|
|
|
|
|
ser.Sync("cpu_deadcounter", ref cpu_deadcounter);
|
2012-12-19 15:46:37 +00:00
|
|
|
|
ser.BeginSection("Board");
|
2011-04-17 22:51:53 +00:00
|
|
|
|
board.SyncState(ser);
|
2012-07-16 22:06:55 +00:00
|
|
|
|
if (board is NESBoardBase && !((NESBoardBase)board).SyncStateFlag)
|
|
|
|
|
throw new InvalidOperationException("the current NES mapper didnt call base.SyncState");
|
2012-12-19 15:46:37 +00:00
|
|
|
|
ser.EndSection();
|
2011-04-17 22:51:53 +00:00
|
|
|
|
ppu.SyncState(ser);
|
2011-06-10 05:02:06 +00:00
|
|
|
|
apu.SyncState(ser);
|
2012-09-29 22:19:49 +00:00
|
|
|
|
|
2012-09-29 08:39:59 +00:00
|
|
|
|
if (version >= 2)
|
2014-05-26 01:29:00 +00:00
|
|
|
|
{
|
2012-09-29 08:39:59 +00:00
|
|
|
|
ser.Sync("DB", ref DB);
|
2014-05-26 01:29:00 +00:00
|
|
|
|
}
|
2014-02-28 04:05:36 +00:00
|
|
|
|
if (version >= 3)
|
|
|
|
|
{
|
|
|
|
|
ser.Sync("latched4016", ref latched4016);
|
|
|
|
|
ser.BeginSection("ControllerDeck");
|
|
|
|
|
ControllerDeck.SyncState(ser);
|
|
|
|
|
ser.EndSection();
|
|
|
|
|
}
|
2014-05-26 01:29:00 +00:00
|
|
|
|
if (version >= 4)
|
|
|
|
|
{
|
|
|
|
|
ser.Sync("resetSignal", ref resetSignal);
|
|
|
|
|
ser.Sync("hardResetSignal", ref hardResetSignal);
|
|
|
|
|
}
|
2012-09-29 08:39:59 +00:00
|
|
|
|
|
2011-04-17 22:51:53 +00:00
|
|
|
|
ser.EndSection();
|
2011-03-01 09:32:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-04-17 22:51:53 +00:00
|
|
|
|
public void SaveStateText(TextWriter writer) { SyncState(Serializer.CreateTextWriter(writer)); }
|
|
|
|
|
public void LoadStateText(TextReader reader) { SyncState(Serializer.CreateTextReader(reader)); }
|
|
|
|
|
public void SaveStateBinary(BinaryWriter bw) { SyncState(Serializer.CreateBinaryWriter(bw)); }
|
|
|
|
|
public void LoadStateBinary(BinaryReader br) { SyncState(Serializer.CreateBinaryReader(br)); }
|
2011-03-01 09:32:12 +00:00
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
|
BinaryWriter bw = new BinaryWriter(ms);
|
|
|
|
|
SaveStateBinary(bw);
|
|
|
|
|
bw.Flush();
|
|
|
|
|
return ms.ToArray();
|
|
|
|
|
}
|
2013-05-06 20:51:28 +00:00
|
|
|
|
|
|
|
|
|
public bool BinarySaveStatesPreferred { get { return false; } }
|
2013-11-11 03:20:33 +00:00
|
|
|
|
|
2014-04-19 22:23:13 +00:00
|
|
|
|
public Dictionary<string, int> GetCpuFlagsAndRegisters()
|
2013-11-11 03:20:33 +00:00
|
|
|
|
{
|
2014-04-19 22:23:13 +00:00
|
|
|
|
return new Dictionary<string, int>
|
|
|
|
|
{
|
|
|
|
|
{ "A", cpu.A },
|
|
|
|
|
{ "X", cpu.X },
|
|
|
|
|
{ "Y", cpu.Y },
|
|
|
|
|
{ "S", cpu.S },
|
|
|
|
|
{ "PC", cpu.PC },
|
|
|
|
|
{ "Flag C", cpu.FlagC ? 1 : 0 },
|
|
|
|
|
{ "Flag Z", cpu.FlagZ ? 1 : 0 },
|
|
|
|
|
{ "Flag I", cpu.FlagI ? 1 : 0 },
|
|
|
|
|
{ "Flag D", cpu.FlagD ? 1 : 0 },
|
|
|
|
|
{ "Flag B", cpu.FlagB ? 1 : 0 },
|
|
|
|
|
{ "Flag V", cpu.FlagV ? 1 : 0 },
|
|
|
|
|
{ "Flag N", cpu.FlagN ? 1 : 0 },
|
|
|
|
|
{ "Flag T", cpu.FlagT ? 1 : 0 }
|
2013-11-11 03:20:33 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
2013-12-22 00:44:39 +00:00
|
|
|
|
|
2014-05-31 17:03:21 +00:00
|
|
|
|
public void SetCpuRegister(string register, int value)
|
|
|
|
|
{
|
|
|
|
|
switch (register)
|
|
|
|
|
{
|
|
|
|
|
default:
|
|
|
|
|
throw new InvalidOperationException();
|
|
|
|
|
case "A":
|
|
|
|
|
cpu.A = (byte)value;
|
|
|
|
|
break;
|
|
|
|
|
case "X":
|
|
|
|
|
cpu.X = (byte)value;
|
|
|
|
|
break;
|
|
|
|
|
case "Y":
|
|
|
|
|
cpu.Y = (byte)value;
|
|
|
|
|
break;
|
|
|
|
|
case "S":
|
|
|
|
|
cpu.S = (byte)value;
|
|
|
|
|
break;
|
|
|
|
|
case "PC":
|
2014-05-31 18:25:36 +00:00
|
|
|
|
cpu.PC = (ushort)value;
|
2014-05-31 17:03:21 +00:00
|
|
|
|
break;
|
|
|
|
|
case "Flag I":
|
|
|
|
|
cpu.FlagI = value > 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-22 00:44:39 +00:00
|
|
|
|
NESSettings Settings = new NESSettings();
|
2014-01-01 03:03:10 +00:00
|
|
|
|
NESSyncSettings SyncSettings = new NESSyncSettings();
|
2013-12-22 00:44:39 +00:00
|
|
|
|
|
|
|
|
|
public object GetSettings() { return Settings.Clone(); }
|
2014-01-01 03:03:10 +00:00
|
|
|
|
public object GetSyncSettings() { return SyncSettings.Clone(); }
|
2013-12-22 00:44:39 +00:00
|
|
|
|
public bool PutSettings(object o)
|
2014-02-28 04:05:36 +00:00
|
|
|
|
{
|
2013-12-22 00:44:39 +00:00
|
|
|
|
Settings = (NESSettings)o;
|
|
|
|
|
if (Settings.ClipLeftAndRight)
|
|
|
|
|
{
|
|
|
|
|
videoProvider.left = 8;
|
2014-02-01 16:57:20 +00:00
|
|
|
|
videoProvider.right = 247;
|
2013-12-22 00:44:39 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
videoProvider.left = 0;
|
|
|
|
|
videoProvider.right = 255;
|
|
|
|
|
}
|
|
|
|
|
CoreComm.ScreenLogicalOffsetX = videoProvider.left;
|
|
|
|
|
CoreComm.ScreenLogicalOffsetY = DisplayType == DisplayType.NTSC ? Settings.NTSC_TopLine : Settings.PAL_TopLine;
|
2013-12-22 06:55:34 +00:00
|
|
|
|
|
|
|
|
|
SetPalette(Settings.Palette);
|
2013-12-22 21:47:16 +00:00
|
|
|
|
|
|
|
|
|
apu.Square1V = Settings.Square1;
|
|
|
|
|
apu.Square2V = Settings.Square2;
|
|
|
|
|
apu.TriangleV = Settings.Triangle;
|
|
|
|
|
apu.NoiseV = Settings.Noise;
|
|
|
|
|
apu.DMCV = Settings.DMC;
|
|
|
|
|
|
2013-12-22 00:44:39 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-01-01 03:03:10 +00:00
|
|
|
|
public bool PutSyncSettings(object o)
|
|
|
|
|
{
|
|
|
|
|
var n = (NESSyncSettings)o;
|
|
|
|
|
bool ret = NESSyncSettings.NeedsReboot(SyncSettings, n);
|
|
|
|
|
SyncSettings = n;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2013-12-22 00:44:39 +00:00
|
|
|
|
|
|
|
|
|
public class NESSettings
|
|
|
|
|
{
|
|
|
|
|
public bool AllowMoreThanEightSprites = false;
|
|
|
|
|
public bool ClipLeftAndRight = false;
|
|
|
|
|
public bool DispBackground = true;
|
|
|
|
|
public bool DispSprites = true;
|
|
|
|
|
public int BackgroundColor = 0;
|
|
|
|
|
|
|
|
|
|
public int NTSC_TopLine = 8;
|
|
|
|
|
public int NTSC_BottomLine = 231;
|
|
|
|
|
public int PAL_TopLine = 0;
|
|
|
|
|
public int PAL_BottomLine = 239;
|
|
|
|
|
|
2013-12-22 06:55:34 +00:00
|
|
|
|
public int[,] Palette;
|
|
|
|
|
|
2013-12-22 21:47:16 +00:00
|
|
|
|
public int Square1 = 376;
|
|
|
|
|
public int Square2 = 376;
|
|
|
|
|
public int Triangle = 426;
|
|
|
|
|
public int Noise = 247;
|
|
|
|
|
public int DMC = 167;
|
|
|
|
|
|
2013-12-22 00:44:39 +00:00
|
|
|
|
public NESSettings Clone()
|
|
|
|
|
{
|
2013-12-23 02:51:41 +00:00
|
|
|
|
var ret = (NESSettings)MemberwiseClone();
|
|
|
|
|
ret.Palette = (int[,])ret.Palette.Clone();
|
|
|
|
|
return ret;
|
2013-12-22 00:44:39 +00:00
|
|
|
|
}
|
2013-12-22 06:55:34 +00:00
|
|
|
|
|
|
|
|
|
public NESSettings()
|
|
|
|
|
{
|
2014-05-13 23:26:39 +00:00
|
|
|
|
Palette = (int[,])Palettes.QuickNESPalette.Clone();
|
2013-12-22 06:55:34 +00:00
|
|
|
|
}
|
2014-02-28 04:05:36 +00:00
|
|
|
|
|
2013-12-22 06:55:34 +00:00
|
|
|
|
[Newtonsoft.Json.JsonConstructor]
|
|
|
|
|
public NESSettings(int[,] Palette)
|
|
|
|
|
{
|
|
|
|
|
if (Palette == null)
|
|
|
|
|
// only needed for SVN purposes
|
2014-05-13 23:26:39 +00:00
|
|
|
|
this.Palette = (int[,])Palettes.QuickNESPalette.Clone();
|
2013-12-22 06:55:34 +00:00
|
|
|
|
else
|
|
|
|
|
this.Palette = Palette;
|
|
|
|
|
}
|
2013-12-22 00:44:39 +00:00
|
|
|
|
}
|
2014-01-01 03:03:10 +00:00
|
|
|
|
|
|
|
|
|
public class NESSyncSettings
|
|
|
|
|
{
|
|
|
|
|
public Dictionary<string, string> BoardProperties = new Dictionary<string, string>();
|
|
|
|
|
|
2014-02-06 02:06:17 +00:00
|
|
|
|
public enum Region
|
|
|
|
|
{
|
|
|
|
|
Default,
|
|
|
|
|
NTSC,
|
|
|
|
|
PAL,
|
|
|
|
|
Dendy
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public Region RegionOverride = Region.Default;
|
|
|
|
|
|
2014-03-04 23:18:10 +00:00
|
|
|
|
public NESControlSettings Controls = new NESControlSettings();
|
|
|
|
|
|
2014-01-01 03:03:10 +00:00
|
|
|
|
public NESSyncSettings Clone()
|
|
|
|
|
{
|
|
|
|
|
var ret = (NESSyncSettings)MemberwiseClone();
|
|
|
|
|
ret.BoardProperties = new Dictionary<string, string>(BoardProperties);
|
2014-03-04 23:18:10 +00:00
|
|
|
|
ret.Controls = Controls.Clone();
|
2014-01-01 03:03:10 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool NeedsReboot(NESSyncSettings x, NESSyncSettings y)
|
|
|
|
|
{
|
2014-03-04 23:18:10 +00:00
|
|
|
|
return !(Util.DictionaryEqual(x.BoardProperties, y.BoardProperties) &&
|
|
|
|
|
x.RegionOverride == y.RegionOverride &&
|
|
|
|
|
!NESControlSettings.NeedsReboot(x.Controls, y.Controls));
|
2014-01-01 03:03:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-07-30 20:49:36 +00:00
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//todo
|
|
|
|
|
//http://blog.ntrq.net/?p=428
|
2011-03-01 07:25:14 +00:00
|
|
|
|
//cpu bus junk bits
|
|
|
|
|
|
|
|
|
|
//UBER DOC
|
|
|
|
|
//http://nocash.emubase.de/everynes.htm
|
|
|
|
|
|
|
|
|
|
//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
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
|
|
|
|
//some ppu tests
|
|
|
|
|
//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
|