2011-02-20 02:49:37 +00:00
|
|
|
|
using System;
|
2011-03-02 06:18:26 +00:00
|
|
|
|
using System.Linq;
|
2011-02-28 10:16:07 +00:00
|
|
|
|
using System.Diagnostics;
|
2011-02-20 02:49:37 +00:00
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using BizHawk.Emulation.CPUs.M6502;
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
//TODO - redo all timekeeping in terms of master clock
|
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
|
|
|
|
{
|
2011-03-08 07:25:35 +00:00
|
|
|
|
|
2011-02-27 09:45:50 +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
|
|
|
|
|
2012-10-21 15:58:24 +00:00
|
|
|
|
public NES(GameInfo game, byte[] rom, byte[] fdsbios = null)
|
2011-02-28 06:16:20 +00:00
|
|
|
|
{
|
2011-06-11 22:15:08 +00:00
|
|
|
|
CoreOutputComm = new CoreOutputComm();
|
2012-09-30 02:07:14 +00:00
|
|
|
|
CoreOutputComm.CpuTraceAvailable = true;
|
2012-10-13 18:59:09 +00:00
|
|
|
|
CoreInputComm = new BizHawk.CoreInputComm();
|
2011-03-08 07:25:35 +00:00
|
|
|
|
BootGodDB.Initialize();
|
2011-03-13 02:48:45 +00:00
|
|
|
|
SetPalette(Palettes.FCEUX_Standard);
|
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
|
|
|
|
ControllerDefinition = new ControllerDefinition(NESController);
|
|
|
|
|
if (board is FDS)
|
|
|
|
|
{
|
|
|
|
|
var b = board as FDS;
|
|
|
|
|
ControllerDefinition.BoolButtons.Add("FDS Eject");
|
|
|
|
|
for (int i = 0; i < b.NumSides; i++)
|
|
|
|
|
ControllerDefinition.BoolButtons.Add("FDS Insert " + i);
|
|
|
|
|
}
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2011-06-11 22:15:08 +00:00
|
|
|
|
public CoreInputComm CoreInputComm { get; set; }
|
|
|
|
|
public CoreOutputComm CoreOutputComm { get; private set; }
|
|
|
|
|
|
2012-10-06 16:56:46 +00:00
|
|
|
|
public DisplayType DisplayType { get { return BizHawk.DisplayType.NTSC; } }
|
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
class MyVideoProvider : IVideoProvider
|
|
|
|
|
{
|
2011-09-04 01:12:12 +00:00
|
|
|
|
public int top = 8;
|
|
|
|
|
public int bottom = 231;
|
2011-09-04 01:58:16 +00:00
|
|
|
|
public int left = 0;
|
2012-03-04 01:41:14 +00:00
|
|
|
|
public int right = 255;
|
2012-08-19 20:01:17 +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
|
|
|
|
{
|
2012-09-20 00:53:21 +00:00
|
|
|
|
int backdrop = 0;
|
|
|
|
|
if (emu.CoreInputComm != null)
|
|
|
|
|
backdrop = emu.CoreInputComm.NES_BackdropColor;
|
2011-06-11 22:15:08 +00:00
|
|
|
|
bool useBackdrop = (backdrop & 0xFF000000) != 0;
|
2011-09-24 17:07:48 +00:00
|
|
|
|
|
2011-03-21 01:49:20 +00:00
|
|
|
|
//TODO - we could recalculate this on the fly (and invalidate/recalculate it when the palette is changed)
|
2012-03-04 01:41:14 +00:00
|
|
|
|
int width = BufferWidth;
|
|
|
|
|
for (int x = left; x <= right; x++)
|
2011-03-21 01:49:20 +00:00
|
|
|
|
{
|
2012-03-04 01:41:14 +00:00
|
|
|
|
for (int y = top; y <= bottom; y++)
|
2011-06-11 22:15:08 +00:00
|
|
|
|
{
|
2012-08-19 19:21:35 +00:00
|
|
|
|
short pixel = emu.ppu.xbuf[(y << 8) + x];
|
2011-09-04 01:12:12 +00:00
|
|
|
|
if ((pixel & 0x8000) != 0 && useBackdrop)
|
|
|
|
|
{
|
2011-09-24 17:07:48 +00:00
|
|
|
|
pixels[((y - top) * width) + (x - left)] = backdrop;
|
2011-09-04 01:12:12 +00:00
|
|
|
|
}
|
2011-09-24 17:07:48 +00:00
|
|
|
|
else pixels[((y - top) * width) + (x - left)] = emu.palette_compiled[pixel & 0x7FFF];
|
2011-06-11 22:15:08 +00:00
|
|
|
|
}
|
2011-03-21 01:49:20 +00:00
|
|
|
|
}
|
2011-02-20 02:49:37 +00:00
|
|
|
|
}
|
2012-08-19 20:01:17 +00:00
|
|
|
|
public int VirtualWidth { get { return BufferWidth; } }
|
2012-03-04 01:41:14 +00:00
|
|
|
|
public int BufferWidth { get { return right - left + 1; } }
|
|
|
|
|
public int BufferHeight { get { return bottom - top + 1; } }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public int BackgroundColor { get { return 0; } }
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-20 00:22:24 +00:00
|
|
|
|
public int FirstDrawLine { get { return videoProvider.top; } set { videoProvider.top = value; CoreOutputComm.ScreenLogicalOffsetY = videoProvider.top; } }
|
2011-09-04 01:12:12 +00:00
|
|
|
|
public int LastDrawLine { get { return videoProvider.bottom; } set { videoProvider.bottom = value; } }
|
|
|
|
|
|
2011-09-04 01:58:16 +00:00
|
|
|
|
public void SetClipLeftAndRight(bool clip)
|
|
|
|
|
{
|
|
|
|
|
if (clip)
|
|
|
|
|
{
|
|
|
|
|
videoProvider.left = 8;
|
|
|
|
|
videoProvider.right = 248;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
videoProvider.left = 0;
|
2012-03-04 01:41:14 +00:00
|
|
|
|
videoProvider.right = 255;
|
2011-09-04 01:58:16 +00:00
|
|
|
|
}
|
2012-09-20 00:24:46 +00:00
|
|
|
|
|
|
|
|
|
CoreOutputComm.ScreenLogicalOffsetX = videoProvider.left;
|
2012-09-20 00:53:21 +00:00
|
|
|
|
videoProvider.FillFrameBuffer();
|
2011-09-04 01:58:16 +00:00
|
|
|
|
}
|
2011-09-24 17:07:48 +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; } }
|
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 ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(magicSoundProvider, 734); } }
|
|
|
|
|
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 = {
|
|
|
|
|
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Select", "P1 Start", "P1 B", "P1 A", "Reset",
|
|
|
|
|
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Select", "P2 Start", "P2 B", "P2 A"
|
|
|
|
|
}
|
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-02-27 11:40:08 +00:00
|
|
|
|
interface IPortDevice
|
|
|
|
|
{
|
|
|
|
|
void Write(int value);
|
|
|
|
|
byte Read();
|
|
|
|
|
void Update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static INPUTC GPC = { ReadGP, 0, StrobeGP, UpdateGP, 0, 0, LogGP, LoadGP };
|
|
|
|
|
class JoypadPortDevice : NullPortDevice
|
|
|
|
|
{
|
|
|
|
|
int state;
|
|
|
|
|
NES nes;
|
2011-06-18 21:47:20 +00:00
|
|
|
|
int player;
|
|
|
|
|
public JoypadPortDevice(NES nes, int player)
|
2011-02-27 11:40:08 +00:00
|
|
|
|
{
|
|
|
|
|
this.nes = nes;
|
2011-06-18 21:47:20 +00:00
|
|
|
|
this.player = player;
|
2011-02-27 11:40:08 +00:00
|
|
|
|
}
|
|
|
|
|
void Strobe()
|
|
|
|
|
{
|
|
|
|
|
value = 0;
|
2012-02-19 07:09:24 +00:00
|
|
|
|
foreach (
|
|
|
|
|
string str in new string[] {
|
|
|
|
|
"P" + (player + 1).ToString() + " Right", "P" + (player + 1).ToString() + " Left",
|
|
|
|
|
"P" + (player + 1).ToString() + " Down", "P" + (player + 1).ToString() + " Up",
|
|
|
|
|
"P" + (player + 1).ToString() + " Start", "P" + (player + 1).ToString() + " Select",
|
|
|
|
|
"P" + (player + 1).ToString() + " B", "P" + (player + 1).ToString() + " A"
|
|
|
|
|
}
|
|
|
|
|
)
|
2011-02-27 11:40:08 +00:00
|
|
|
|
{
|
|
|
|
|
value <<= 1;
|
|
|
|
|
value |= nes.Controller.IsPressed(str) ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public override void Write(int value)
|
|
|
|
|
{
|
|
|
|
|
if (state == 1 && value == 0)
|
|
|
|
|
Strobe();
|
|
|
|
|
state = value;
|
|
|
|
|
}
|
|
|
|
|
public override byte Read()
|
|
|
|
|
{
|
2011-07-30 20:49:36 +00:00
|
|
|
|
int ret = value & 1;
|
2011-02-27 11:40:08 +00:00
|
|
|
|
value >>= 1;
|
2012-09-29 08:39:59 +00:00
|
|
|
|
return (byte)(ret | nes.DB);
|
2011-02-27 11:40:08 +00:00
|
|
|
|
}
|
|
|
|
|
public override void Update()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
int value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class NullPortDevice : IPortDevice
|
|
|
|
|
{
|
|
|
|
|
public virtual void Write(int value)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
public virtual byte Read()
|
|
|
|
|
{
|
|
|
|
|
return 0xFF;
|
|
|
|
|
}
|
|
|
|
|
public virtual void Update()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
public void ResetFrameCounter()
|
|
|
|
|
{
|
|
|
|
|
_frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
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-09-14 22:28:38 +00:00
|
|
|
|
if (board == null || board.SaveRam == null)
|
|
|
|
|
return null;
|
|
|
|
|
return (byte[])board.SaveRam.Clone();
|
|
|
|
|
}
|
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
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()
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
{
|
2011-03-01 07:25:14 +00:00
|
|
|
|
get { if (board == null) return false; if (board.SaveRam == null) return false; return true; }
|
2011-02-20 02:49:37 +00:00
|
|
|
|
set { }
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
private IList<MemoryDomain> 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>();
|
2011-03-08 07:25:35 +00:00
|
|
|
|
var RAM = new MemoryDomain("RAM", 0x800, Endian.Little,
|
2011-07-30 20:49:36 +00:00
|
|
|
|
addr => ram[addr & 0x07FF], (addr, value) => ram[addr & 0x07FF] = value);
|
|
|
|
|
var SystemBus = new MemoryDomain("System Bus", 0x10000, Endian.Little,
|
2012-09-01 18:34:31 +00:00
|
|
|
|
addr => ReadMemory((ushort)addr), (addr, value) => ApplySystemBusPoke(addr, value)); //WriteMemory((ushort)addr, value));
|
2011-07-30 20:49:36 +00:00
|
|
|
|
var PPUBus = new MemoryDomain("PPU Bus", 0x4000, Endian.Little,
|
|
|
|
|
addr => ppu.ppubus_peek(addr), (addr, value) => ppu.ppubus_write(addr, value));
|
2011-03-16 03:13:51 +00:00
|
|
|
|
var CIRAMdomain = new MemoryDomain("CIRAM (nametables)", 0x800, Endian.Little,
|
2011-03-08 07:25:35 +00:00
|
|
|
|
addr => CIRAM[addr & 0x07FF], (addr, value) => CIRAM[addr & 0x07FF] = value);
|
2011-08-28 19:07:33 +00:00
|
|
|
|
var OAMdoman = new MemoryDomain("OAM", 64 * 4, Endian.Unknown,
|
|
|
|
|
addr => ppu.OAM[addr & (64 * 4 - 1)], (addr, value) => ppu.OAM[addr & (64 * 4 - 1)] = 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
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (board.SaveRam != null)
|
|
|
|
|
{
|
|
|
|
|
var BatteryRam = new MemoryDomain("Battery RAM", board.SaveRam.Length, Endian.Little,
|
|
|
|
|
addr => board.SaveRam[addr], (addr, value) => board.SaveRam[addr] = value);
|
|
|
|
|
domains.Add(BatteryRam);
|
|
|
|
|
}
|
2011-03-06 03:07:25 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
var PRGROM = new MemoryDomain("PRG ROM", cart.prg_size * 1024, Endian.Little,
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
|
var CHRROM = new MemoryDomain("CHR VROM", cart.chr_size * 1024, 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)
|
|
|
|
|
{
|
2011-03-08 07:25:35 +00:00
|
|
|
|
var VRAM = new MemoryDomain("VRAM", board.VRAM.Length, 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)
|
|
|
|
|
{
|
2011-03-08 07:25:35 +00:00
|
|
|
|
var WRAM = new MemoryDomain("WRAM", board.WRAM.Length, 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
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
memoryDomains = domains.AsReadOnly();
|
|
|
|
|
}
|
2011-03-05 03:03:47 +00:00
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public string SystemId { get { return "NES"; } }
|
2011-03-05 03:03:47 +00:00
|
|
|
|
public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } }
|
|
|
|
|
public MemoryDomain MainMemory { get { return memoryDomains[0]; } }
|
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
|
|
|
|
|
{
|
|
|
|
|
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-10-21 15:58:24 +00:00
|
|
|
|
public unsafe 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;
|
|
|
|
|
List<string> hash_sha1_several = new List<string>();
|
|
|
|
|
string hash_sha1 = null, hash_md5 = null;
|
|
|
|
|
Unif unif = null;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("Found UNIF header:");
|
|
|
|
|
LoadWriteLine("Since this is UNIF we can confidently parse PRG/CHR banks to hash.");
|
|
|
|
|
unif = new Unif(new MemoryStream(file));
|
|
|
|
|
hash_sha1 = unif.GetCartInfo().sha1;
|
|
|
|
|
hash_sha1_several.Add(hash_sha1);
|
2012-10-17 00:59:22 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_sha1);
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
2012-10-21 15:58:24 +00:00
|
|
|
|
else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A")))
|
|
|
|
|
{
|
|
|
|
|
// there's not much else to do with FDS images other than to feed them to the board
|
|
|
|
|
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-21 19:22:22 +00:00
|
|
|
|
fdsboard.diskimage = rom;
|
2012-10-21 15:58:24 +00:00
|
|
|
|
fdsboard.Create(this);
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
HardReset();
|
|
|
|
|
SetupMemoryDomains();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fixed (byte* bfile = &file[0])
|
|
|
|
|
{
|
|
|
|
|
var header = (iNES_HEADER*)bfile;
|
|
|
|
|
if (!header->CheckID()) throw new InvalidOperationException("iNES header not found");
|
|
|
|
|
header->Cleanup();
|
2011-02-27 09:45:50 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +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
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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);
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
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.");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_sha1);
|
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_md5);
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +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();
|
|
|
|
|
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);
|
|
|
|
|
}
|
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)
|
|
|
|
|
choice = IdentifyFromBootGodDB(hash_sha1_several);
|
|
|
|
|
if (choice == null)
|
|
|
|
|
{
|
|
|
|
|
LoadWriteLine("Could not locate game in nescartdb");
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (USE_DATABASE)
|
2012-10-16 22:27:48 +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)
|
2011-02-28 06:16:20 +00:00
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("Could not locate game in bizhawk gamedb");
|
|
|
|
|
if (unif != null)
|
2011-03-19 20:12:06 +00:00
|
|
|
|
{
|
2012-10-16 22:27:48 +00:00
|
|
|
|
LoadWriteLine("Using information from UNIF header");
|
|
|
|
|
choice = unif.GetCartInfo();
|
|
|
|
|
choice.game = new NESGameInfo();
|
|
|
|
|
choice.game.name = gameInfo.Name;
|
|
|
|
|
origin = EDetectionOrigin.UNIF;
|
2011-03-19 20:12:06 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
if (iNesHeaderInfo != null)
|
2011-03-19 20:12:06 +00:00
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Attempting inference from iNES header");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
choice = iNesHeaderInfo;
|
2011-03-19 20:12:06 +00:00
|
|
|
|
string iNES_board = iNESBoardDetector.Detect(choice);
|
|
|
|
|
if (iNES_board == null)
|
|
|
|
|
throw new Exception("couldnt identify NES rom");
|
|
|
|
|
choice.board_type = iNES_board;
|
2011-06-13 08:38:10 +00:00
|
|
|
|
|
|
|
|
|
//try spinning up a board with 8K wram and with 0K wram to see if one answers
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
boardType = FindBoard(choice, origin);
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
if (boardType != null)
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Ambiguous iNES wram size resolved as {0}k", choice.wram_size);
|
2011-06-13 08:38:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Chose board from iNES heuristics: " + iNES_board);
|
2011-08-04 03:20:54 +00:00
|
|
|
|
choice.game.name = gameInfo.Name;
|
2011-03-19 20:12:06 +00:00
|
|
|
|
origin = EDetectionOrigin.INES;
|
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
}
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (choice.prg_size == -1) choice.prg_size = unif.GetCartInfo().prg_size;
|
|
|
|
|
if (choice.chr_size == -1) choice.chr_size = unif.GetCartInfo().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;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-20 20:42:12 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LoadWriteLine("Chose board from nescartdb:");
|
|
|
|
|
LoadWriteLine(choice);
|
|
|
|
|
origin = EDetectionOrigin.BootGodDB;
|
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
//TODO - generate better name with region and system
|
|
|
|
|
game_name = choice.game.name;
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
//find a INESBoard to handle this
|
|
|
|
|
boardType = FindBoard(choice, origin);
|
|
|
|
|
if (boardType == null)
|
|
|
|
|
throw new Exception("No class implements the necessary board type: " + choice.board_type);
|
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);
|
|
|
|
|
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;
|
|
|
|
|
CoreOutputComm.RomStatusAnnotation = "Identified from BootGod's database";
|
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.UNIF)
|
|
|
|
|
{
|
|
|
|
|
RomStatus = RomStatus.NotInDatabase;
|
|
|
|
|
CoreOutputComm.RomStatusAnnotation = "Inferred from UNIF header; somewhat suspicious";
|
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.INES)
|
|
|
|
|
{
|
|
|
|
|
RomStatus = RomStatus.NotInDatabase;
|
|
|
|
|
CoreOutputComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong";
|
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.GameDB)
|
|
|
|
|
{
|
|
|
|
|
if (choice.bad)
|
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();
|
|
|
|
|
CoreOutputComm.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;
|
|
|
|
|
Array.Copy(file, 16 + vrom_offset, board.VROM, 0, board.VROM.Length);
|
2011-03-01 07:25:14 +00:00
|
|
|
|
}
|
2012-10-16 22:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
board.ROM = unif.GetPRG();
|
|
|
|
|
board.VROM = unif.GetCHR();
|
|
|
|
|
}
|
2011-03-08 07:25:35 +00:00
|
|
|
|
|
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
|
|
|
|
|
2012-10-16 22:27:48 +00:00
|
|
|
|
HardReset();
|
2011-03-07 10:41:46 +00:00
|
|
|
|
SetupMemoryDomains();
|
2011-02-27 09:45:50 +00:00
|
|
|
|
}
|
2011-03-01 09:32:12 +00:00
|
|
|
|
|
2011-04-17 22:51:53 +00:00
|
|
|
|
void SyncState(Serializer ser)
|
2011-03-01 09:32:12 +00:00
|
|
|
|
{
|
2012-09-29 08:39:59 +00:00
|
|
|
|
int version = 2;
|
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);
|
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");
|
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)
|
|
|
|
|
ser.Sync("DB", ref DB);
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
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
|