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
|
|
|
|
|
2011-08-04 03:20:54 +00:00
|
|
|
|
public NES(GameInfo game, byte[] rom)
|
2011-02-28 06:16:20 +00:00
|
|
|
|
{
|
2011-06-11 22:15:08 +00:00
|
|
|
|
CoreOutputComm = new CoreOutputComm();
|
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);
|
2011-09-24 17:07:48 +00:00
|
|
|
|
Init(game, rom);
|
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
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetGameGenie(int check, int replace)
|
|
|
|
|
{
|
|
|
|
|
flags |= EFlags.GameGenie;
|
|
|
|
|
gg_check = check;
|
|
|
|
|
gg_replace = replace;
|
|
|
|
|
Sync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool HasGameGenie { get { return (flags & EFlags.GameGenie) != 0; } }
|
|
|
|
|
public byte ApplyGameGenie(byte curr)
|
|
|
|
|
{
|
|
|
|
|
if (!HasGameGenie) return curr;
|
|
|
|
|
if (curr == gg_check || gg_check == -1) { Console.WriteLine("applied game genie"); return (byte)gg_replace; }
|
|
|
|
|
else return curr;
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
|
int gg_check, gg_replace;
|
|
|
|
|
|
|
|
|
|
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; }
|
|
|
|
|
|
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;
|
2011-09-04 01:12:12 +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()
|
|
|
|
|
{
|
2011-06-11 22:15:08 +00:00
|
|
|
|
int backdrop = emu.CoreInputComm.NES_BackdropColor;
|
|
|
|
|
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
|
|
|
|
{
|
2011-09-24 17:07:48 +00:00
|
|
|
|
short pixel = emu.ppu.xbuf[(y * 256) + 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
|
|
|
|
return pixels;
|
|
|
|
|
}
|
2012-06-25 02:50:34 +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; } }
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-04 01:12:12 +00:00
|
|
|
|
public int FirstDrawLine { get { return videoProvider.top; } set { videoProvider.top = value; } }
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
}
|
2011-09-24 17:07:48 +00:00
|
|
|
|
|
2011-03-21 01:51:06 +00:00
|
|
|
|
MyVideoProvider videoProvider;
|
|
|
|
|
public IVideoProvider VideoProvider { get { return videoProvider; } }
|
2011-03-13 00:34:24 +00:00
|
|
|
|
public ISoundProvider SoundProvider { get { return apu; } }
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public ControllerDefinition ControllerDefinition { get { return NESController; } }
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
return (byte)ret;
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
2011-02-20 02:49:37 +00:00
|
|
|
|
public bool DeterministicEmulation { get { return true; } set { } }
|
|
|
|
|
|
2011-03-01 07:25:14 +00:00
|
|
|
|
public byte[] SaveRam
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (board == null) return null;
|
2011-03-01 07:25:14 +00:00
|
|
|
|
return board.SaveRam;
|
|
|
|
|
}
|
|
|
|
|
}
|
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,
|
|
|
|
|
addr => ReadMemory((ushort)addr), (addr, value) => WriteMemory((ushort)addr, value));
|
|
|
|
|
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-03-17 03:51:31 +00:00
|
|
|
|
//demo a game genie code
|
|
|
|
|
GetWatch(NESWatch.EDomain.Sysbus, 0xB424).SetGameGenie(-1, 0x10);
|
|
|
|
|
GetWatch(NESWatch.EDomain.Sysbus, 0xB424).RemoveGameGenie();
|
2011-03-16 05:06:21 +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
|
|
|
|
{
|
|
|
|
|
None, BootGodDB, GameDB, INES
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-04 03:20:54 +00:00
|
|
|
|
public unsafe void Init(GameInfo gameInfo, byte[] rom)
|
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;
|
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")))
|
|
|
|
|
throw new Exception("You've tried to open a UNIF rom. We don't have any UNIF roms to test with. Please consult the developers.");
|
2011-02-27 09:45:50 +00:00
|
|
|
|
fixed (byte* bfile = &file[0])
|
|
|
|
|
{
|
2011-03-19 20:12:06 +00:00
|
|
|
|
var origin = EDetectionOrigin.None;
|
|
|
|
|
|
2011-02-27 09:45:50 +00:00
|
|
|
|
var header = (iNES_HEADER*)bfile;
|
|
|
|
|
if (!header->CheckID()) throw new InvalidOperationException("iNES header not found");
|
|
|
|
|
header->Cleanup();
|
|
|
|
|
|
2011-02-28 06:16:20 +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
|
|
|
|
|
|
|
|
|
List<string> hash_sha1_several = new List<string>();
|
|
|
|
|
string hash_sha1 = "sha1:" + Util.Hash_SHA1(file,16,file.Length - 16);
|
|
|
|
|
hash_sha1_several.Add(hash_sha1);
|
|
|
|
|
string hash_md5 = "md5:" + Util.Hash_MD5(file, 16, file.Length - 16);
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Found iNES header:");
|
2012-03-06 08:01:48 +00:00
|
|
|
|
CartInfo iNesHeaderInfo = header->Analyze(new MyWriter(LoadReport));
|
2012-03-06 07:51:41 +00:00
|
|
|
|
LoadWriteLine("Since this is iNES we can (somewhat) confidently parse PRG/CHR banks to hash.");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_sha1);
|
2012-03-07 19:14:15 +00:00
|
|
|
|
LoadWriteLine("headerless rom hash: {0}", hash_md5);
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
2012-03-06 07:51:41 +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);
|
2012-03-06 08:01:48 +00:00
|
|
|
|
LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash);
|
2012-03-06 07:51:41 +00:00
|
|
|
|
hash_sha1_several.Add(hash);
|
2012-03-07 19:14:15 +00:00
|
|
|
|
hash = "md5:" + Util.Hash_MD5(bytes, 0, bytes.Length);
|
|
|
|
|
LoadWriteLine(" PRG (8KB) + CHR hash: {0}", hash);
|
2012-03-06 07:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-06-13 08:38:10 +00:00
|
|
|
|
Type boardType = null;
|
2011-03-19 20:12:06 +00:00
|
|
|
|
CartInfo choice = null;
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (USE_DATABASE)
|
2012-03-06 07:51:41 +00:00
|
|
|
|
choice = IdentifyFromBootGodDB(hash_sha1_several);
|
2011-03-20 20:42:12 +00:00
|
|
|
|
if (choice == null)
|
2011-02-28 06:16:20 +00:00
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Could not locate game in nescartdb");
|
2011-03-19 20:12:06 +00:00
|
|
|
|
if (USE_DATABASE)
|
|
|
|
|
{
|
|
|
|
|
choice = IdentifyFromGameDB(hash_md5);
|
|
|
|
|
if (choice == null)
|
2011-06-08 01:03:32 +00:00
|
|
|
|
{
|
2011-03-19 20:12:06 +00:00
|
|
|
|
choice = IdentifyFromGameDB(hash_sha1);
|
2011-06-08 01:03:32 +00:00
|
|
|
|
}
|
2011-03-19 20:12:06 +00:00
|
|
|
|
}
|
|
|
|
|
if (choice == null)
|
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Could not locate game in bizhawk gamedb");
|
|
|
|
|
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-03-07 10:41:46 +00:00
|
|
|
|
else
|
2011-03-19 20:12:06 +00:00
|
|
|
|
{
|
|
|
|
|
origin = EDetectionOrigin.GameDB;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Chose board from bizhawk gamedb: " + choice.board_type);
|
2012-04-14 08:28:42 +00:00
|
|
|
|
//gamedb entries that dont specify prg/chr sizes can infer it from the ines header
|
|
|
|
|
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;
|
2011-03-19 20:12:06 +00:00
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
}
|
2011-03-20 20:42:12 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("Chose board from nescartdb:");
|
|
|
|
|
LoadWriteLine(choice);
|
2011-03-20 20:42:12 +00:00
|
|
|
|
origin = EDetectionOrigin.BootGodDB;
|
|
|
|
|
}
|
2011-02-28 06:16:20 +00:00
|
|
|
|
|
2011-06-08 06:17:41 +00:00
|
|
|
|
//TODO - generate better name with region and system
|
2011-03-08 07:25:35 +00:00
|
|
|
|
game_name = choice.game.name;
|
2011-03-07 10:41:46 +00:00
|
|
|
|
|
|
|
|
|
//find a INESBoard to handle this
|
2011-04-18 01:57:22 +00:00
|
|
|
|
boardType = FindBoard(choice, origin);
|
2011-03-19 20:12:06 +00:00
|
|
|
|
if (boardType == null)
|
|
|
|
|
throw new Exception("No class implements the necessary board type: " + choice.board_type);
|
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
if (choice.DB_GameInfo != null)
|
2012-04-09 06:38:28 +00:00
|
|
|
|
choice.bad = choice.DB_GameInfo.IsRomStatusBad();
|
2011-07-10 21:00:28 +00:00
|
|
|
|
|
|
|
|
|
LoadWriteLine("Final game detection results:");
|
|
|
|
|
LoadWriteLine(choice);
|
|
|
|
|
LoadWriteLine("\"" + game_name + "\"");
|
|
|
|
|
LoadWriteLine("Implemented by: class " + boardType.Name);
|
2011-06-08 06:53:11 +00:00
|
|
|
|
if (choice.bad)
|
|
|
|
|
{
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("~~ ONE WAY OR ANOTHER, THIS DUMP IS KNOWN TO BE *BAD* ~~");
|
|
|
|
|
LoadWriteLine("~~ YOU SHOULD FIND A BETTER FILE ~~");
|
2011-06-08 06:53:11 +00:00
|
|
|
|
}
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
LoadWriteLine("END NES rom analysis");
|
|
|
|
|
LoadWriteLine("------");
|
2011-06-08 06:17:41 +00:00
|
|
|
|
|
2011-03-07 10:41:46 +00:00
|
|
|
|
board = (INESBoard)Activator.CreateInstance(boardType);
|
2011-02-28 06:16:20 +00:00
|
|
|
|
|
2011-03-08 07:25:35 +00:00
|
|
|
|
cart = choice;
|
2011-03-07 10:41:46 +00:00
|
|
|
|
board.Create(this);
|
2011-03-20 02:12:10 +00:00
|
|
|
|
board.Configure(origin);
|
2011-03-03 19:56:16 +00:00
|
|
|
|
|
2011-07-10 21:00:28 +00:00
|
|
|
|
if (origin == EDetectionOrigin.BootGodDB)
|
|
|
|
|
{
|
2011-08-04 03:20:54 +00:00
|
|
|
|
RomStatus = RomStatus.GoodDump;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
CoreOutputComm.RomStatusAnnotation = "Identified from BootGod's database";
|
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.INES)
|
|
|
|
|
{
|
2011-08-04 03:20:54 +00:00
|
|
|
|
RomStatus = RomStatus.NotInDatabase;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
CoreOutputComm.RomStatusAnnotation = "Inferred from iNES header; potentially wrong";
|
|
|
|
|
}
|
|
|
|
|
if (origin == EDetectionOrigin.GameDB)
|
|
|
|
|
{
|
|
|
|
|
if (choice.bad)
|
|
|
|
|
{
|
2011-08-04 03:20:54 +00:00
|
|
|
|
RomStatus = RomStatus.BadDump;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-08-04 03:20:54 +00:00
|
|
|
|
RomStatus = choice.DB_GameInfo.Status;
|
2011-07-10 21:00:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadReport.Flush();
|
|
|
|
|
CoreOutputComm.RomStatusDetails = LoadReport.ToString();
|
|
|
|
|
|
2011-03-08 07:25:35 +00:00
|
|
|
|
//create the board's rom and vrom
|
|
|
|
|
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
|
|
|
|
}
|
2011-03-08 07:25:35 +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-06-23 08:52:12 +00:00
|
|
|
|
board.PostConfigure();
|
|
|
|
|
|
2011-03-07 10:41:46 +00:00
|
|
|
|
HardReset();
|
|
|
|
|
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
|
|
|
|
{
|
2011-04-17 22:51:53 +00:00
|
|
|
|
ser.BeginSection("NES");
|
|
|
|
|
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);
|
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-06-02 02:59:18 +00:00
|
|
|
|
|
2011-07-30 20:49:36 +00:00
|
|
|
|
public void Dispose() { }
|
|
|
|
|
}
|
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
|